Commit 14c8e7121ad088372c9d4608f70a2da73b569e37

Authored by xp.Huang
2 parents fdf1307c 26525b9a

Merge branch 'master_dev' into 'master'

fix:云台控制传参参数错误

See merge request yunteng/thingskit!353
Showing 57 changed files with 4091 additions and 184 deletions

Too many changes to show.

To preserve performance only 57 of 202 files are displayed.

@@ -92,6 +92,10 @@ @@ -92,6 +92,10 @@
92 </dependency> 92 </dependency>
93 <dependency> 93 <dependency>
94 <groupId>org.thingsboard.common.transport</groupId> 94 <groupId>org.thingsboard.common.transport</groupId>
  95 + <artifactId>gbt28181</artifactId>
  96 + </dependency>
  97 + <dependency>
  98 + <groupId>org.thingsboard.common.transport</groupId>
95 <artifactId>http</artifactId> 99 <artifactId>http</artifactId>
96 </dependency> 100 </dependency>
97 <dependency> 101 <dependency>
  1 +--更新区域表的名称
  2 +update sys_area
  3 +set name ='澳门特别行政区'
  4 +where code = 820000
  5 + and "level" = 'PROVINCE';
  6 +update sys_area
  7 +set name ='香港特别行政区'
  8 +where code = 810000
  9 + and "level" = 'PROVINCE';
  10 +UPDATE "public"."sys_area"
  11 +SET "name" = '香港特别行政区',
  12 + "level" = 'CITY'
  13 +WHERE "code" = 810000
  14 + AND "parent_id" = 810000;
  15 +
  16 +UPDATE "public"."sys_area"
  17 +SET "name" = '澳门特别行政区',
  18 + "level" = 'CITY'
  19 +WHERE "code" = 820000
  20 + AND "parent_id" = 820000;
  21 +ALTER TABLE tk_device_camera ALTER COLUMN name TYPE varchar(128);
  22 +DROP TABLE IF EXISTS "public"."tk_media_server";
  23 +CREATE TABLE "public"."tk_media_server"
  24 +(
  25 + "id" varchar(36) COLLATE "pg_catalog"."default" NOT NULL,
  26 + "ip" varchar(20) COLLATE "pg_catalog"."default",
  27 + "hook_ip" varchar(20) COLLATE "pg_catalog"."default",
  28 + "sdp_ip" varchar(20) COLLATE "pg_catalog"."default",
  29 + "stream_ip" varchar(20) COLLATE "pg_catalog"."default",
  30 + "http_port" int4,
  31 + "http_ssl_port" int4,
  32 + "rtmp_port" int4,
  33 + "rtmp_ssl_port" int4,
  34 + "rtp_proxy_port" int4,
  35 + "rtsp_port" int4,
  36 + "rtsp_ssl_port" int4,
  37 + "secret" varchar(50) COLLATE "pg_catalog"."default",
  38 + "rtp_enable" bool,
  39 + "rtp_port_range" varchar(20) COLLATE "pg_catalog"."default",
  40 + "send_rtp_port_range" varchar(20) COLLATE "pg_catalog"."default",
  41 + "record_assist_port" int4,
  42 + "status" int2,
  43 + "tenant_id" varchar(36) COLLATE "pg_catalog"."default",
  44 + "creator" char(36) COLLATE "pg_catalog"."default",
  45 + "create_time" timestamp(6),
  46 + "updater" char(36) COLLATE "pg_catalog"."default",
  47 + "update_time" timestamp(6),
  48 + "remark" varchar(255) COLLATE "pg_catalog"."default",
  49 + "default_server" bool,
  50 + "media_server_id" varchar(36) COLLATE "pg_catalog"."default",
  51 + "auto_config" bool,
  52 + "hook_alive_interval" float4
  53 +)
  54 +;
  55 +COMMENT
  56 +ON COLUMN "public"."tk_media_server"."id" IS '主键ID';
  57 +COMMENT
  58 +ON COLUMN "public"."tk_media_server"."ip" IS 'IP';
  59 +COMMENT
  60 +ON COLUMN "public"."tk_media_server"."hook_ip" IS 'zlmediakit访问平台的IP';
  61 +COMMENT
  62 +ON COLUMN "public"."tk_media_server"."sdp_ip" IS 'SDP IP';
  63 +COMMENT
  64 +ON COLUMN "public"."tk_media_server"."stream_ip" IS '流IP';
  65 +COMMENT
  66 +ON COLUMN "public"."tk_media_server"."http_port" IS 'zlmediakit的HTTP端口';
  67 +COMMENT
  68 +ON COLUMN "public"."tk_media_server"."http_ssl_port" IS 'zlmediakit的HTTPS端口';
  69 +COMMENT
  70 +ON COLUMN "public"."tk_media_server"."rtmp_port" IS 'zlmediakit的RTMP端口';
  71 +COMMENT
  72 +ON COLUMN "public"."tk_media_server"."rtmp_ssl_port" IS 'zlmediakit的RTMPS端口';
  73 +COMMENT
  74 +ON COLUMN "public"."tk_media_server"."rtp_proxy_port" IS 'RTP收流端口(单端口模式有用)';
  75 +COMMENT
  76 +ON COLUMN "public"."tk_media_server"."rtsp_port" IS 'zlmediakit的RTSP端口';
  77 +COMMENT
  78 +ON COLUMN "public"."tk_media_server"."rtsp_ssl_port" IS 'zlmediakit的RTSPS端口';
  79 +COMMENT
  80 +ON COLUMN "public"."tk_media_server"."secret" IS 'zlmediakit的鉴权参数';
  81 +COMMENT
  82 +ON COLUMN "public"."tk_media_server"."rtp_enable" IS '是否使用多端口模式';
  83 +COMMENT
  84 +ON COLUMN "public"."tk_media_server"."rtp_port_range" IS '多端口RTP收流端口范围';
  85 +COMMENT
  86 +ON COLUMN "public"."tk_media_server"."send_rtp_port_range" IS '多端口RTP发流端口范围';
  87 +COMMENT
  88 +ON COLUMN "public"."tk_media_server"."record_assist_port" IS '录像辅助端口:0代表未使用';
  89 +COMMENT
  90 +ON COLUMN "public"."tk_media_server"."status" IS '流媒体服务状态:0离线 1在线';
  91 +COMMENT
  92 +ON COLUMN "public"."tk_media_server"."tenant_id" IS '租户ID';
  93 +COMMENT
  94 +ON COLUMN "public"."tk_media_server"."creator" IS '创建用户';
  95 +COMMENT
  96 +ON COLUMN "public"."tk_media_server"."create_time" IS '创建时间';
  97 +COMMENT
  98 +ON COLUMN "public"."tk_media_server"."updater" IS '更新用户';
  99 +COMMENT
  100 +ON COLUMN "public"."tk_media_server"."update_time" IS '更新时间';
  101 +COMMENT
  102 +ON COLUMN "public"."tk_media_server"."remark" IS '备注';
  103 +COMMENT
  104 +ON COLUMN "public"."tk_media_server"."default_server" IS '是否系统默认服务';
  105 +COMMENT
  106 +ON COLUMN "public"."tk_media_server"."media_server_id" IS '流媒体ID';
  107 +COMMENT
  108 +ON COLUMN "public"."tk_media_server"."auto_config" IS '是否开启自动配置ZLM';
  109 +COMMENT
  110 +ON COLUMN "public"."tk_media_server"."hook_alive_interval" IS 'keepalive hook触发间隔,单位秒';
  111 +COMMENT
  112 +ON TABLE "public"."tk_media_server" IS '流媒体表(ZLMediakit)GBT28181';
  113 +
  114 +-- ----------------------------
  115 +-- Primary Key structure for table tk_media_server
  116 +-- ----------------------------
  117 +ALTER TABLE "public"."tk_media_server"
  118 + ADD CONSTRAINT "tk_media_server_pkey" PRIMARY KEY ("id");
  119 +
  120 +
  121 +DROP TABLE IF EXISTS "public"."tk_video_channel";
  122 +CREATE TABLE "public"."tk_video_channel"
  123 +(
  124 + "id" varchar(36) COLLATE "pg_catalog"."default" NOT NULL,
  125 + "name" varchar(36) COLLATE "pg_catalog"."default",
  126 + "camera_code" varchar(50) COLLATE "pg_catalog"."default",
  127 + "manufacturer" varchar(50) COLLATE "pg_catalog"."default",
  128 + "model" varchar(36) COLLATE "pg_catalog"."default",
  129 + "owner" varchar(36) COLLATE "pg_catalog"."default",
  130 + "civil_code" varchar(50) COLLATE "pg_catalog"."default",
  131 + "address" varchar(255) COLLATE "pg_catalog"."default",
  132 + "parental" int2,
  133 + "safety_way" int2 DEFAULT 0,
  134 + "register_way" int2 DEFAULT 1,
  135 + "cert_num" varchar(50) COLLATE "pg_catalog"."default",
  136 + "certifiable" int2 DEFAULT 0,
  137 + "error_code" int2 DEFAULT 1,
  138 + "end_time" timestamp(6),
  139 + "secrecy" int2 DEFAULT 0,
  140 + "ip_address" varchar(36) COLLATE "pg_catalog"."default",
  141 + "port" int4,
  142 + "password" varchar(128) COLLATE "pg_catalog"."default",
  143 + "status" varchar(20) COLLATE "pg_catalog"."default",
  144 + "tenant_id" varchar(36) COLLATE "pg_catalog"."default" NOT NULL,
  145 + "creator" char(36) COLLATE "pg_catalog"."default",
  146 + "create_time" timestamp(6),
  147 + "updater" char(36) COLLATE "pg_catalog"."default",
  148 + "update_time" timestamp(6),
  149 + "remark" varchar(255) COLLATE "pg_catalog"."default",
  150 + "channel_id" varchar(50) COLLATE "pg_catalog"."default",
  151 + "stream_id" varchar(50) COLLATE "pg_catalog"."default",
  152 + "device_id" varchar(36) COLLATE "pg_catalog"."default"
  153 +)
  154 +;
  155 +COMMENT
  156 +ON COLUMN "public"."tk_video_channel"."id" IS '视频通道编码ID';
  157 +COMMENT
  158 +ON COLUMN "public"."tk_video_channel"."name" IS '视频通道名称';
  159 +COMMENT
  160 +ON COLUMN "public"."tk_video_channel"."camera_code" IS '设备国标编号';
  161 +COMMENT
  162 +ON COLUMN "public"."tk_video_channel"."manufacturer" IS '制造厂商';
  163 +COMMENT
  164 +ON COLUMN "public"."tk_video_channel"."model" IS '型号';
  165 +COMMENT
  166 +ON COLUMN "public"."tk_video_channel"."owner" IS '设备归属';
  167 +COMMENT
  168 +ON COLUMN "public"."tk_video_channel"."civil_code" IS '行政区域';
  169 +COMMENT
  170 +ON COLUMN "public"."tk_video_channel"."address" IS '安装地址';
  171 +COMMENT
  172 +ON COLUMN "public"."tk_video_channel"."parental" IS '是否有子设备: 0没有 1有';
  173 +COMMENT
  174 +ON COLUMN "public"."tk_video_channel"."safety_way" IS '信令安全模式缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/
  175 +MIME加密签名同时采用方式;4:数字摘要方式';
  176 +COMMENT
  177 +ON COLUMN "public"."tk_video_channel"."register_way" IS '注册方式缺省为1;1:符合IETFRFC3261标准的认证注册模
  178 +式;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式';
  179 +COMMENT
  180 +ON COLUMN "public"."tk_video_channel"."cert_num" IS '证书序列号';
  181 +COMMENT
  182 +ON COLUMN "public"."tk_video_channel"."certifiable" IS '证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1:
  183 +有效';
  184 +COMMENT
  185 +ON COLUMN "public"."tk_video_channel"."error_code" IS '无效原因码';
  186 +COMMENT
  187 +ON COLUMN "public"."tk_video_channel"."end_time" IS '证书终止有效期';
  188 +COMMENT
  189 +ON COLUMN "public"."tk_video_channel"."secrecy" IS '保密属性缺省为0;0:不涉密,1:涉密';
  190 +COMMENT
  191 +ON COLUMN "public"."tk_video_channel"."ip_address" IS '系统IP地址';
  192 +COMMENT
  193 +ON COLUMN "public"."tk_video_channel"."port" IS '端口';
  194 +COMMENT
  195 +ON COLUMN "public"."tk_video_channel"."password" IS '密码';
  196 +COMMENT
  197 +ON COLUMN "public"."tk_video_channel"."status" IS '通道状态:ON 在线 OFF 离线,使用枚举值';
  198 +COMMENT
  199 +ON COLUMN "public"."tk_video_channel"."tenant_id" IS '租户ID';
  200 +COMMENT
  201 +ON COLUMN "public"."tk_video_channel"."creator" IS '创建人';
  202 +COMMENT
  203 +ON COLUMN "public"."tk_video_channel"."create_time" IS '创建时间';
  204 +COMMENT
  205 +ON COLUMN "public"."tk_video_channel"."updater" IS '更新人';
  206 +COMMENT
  207 +ON COLUMN "public"."tk_video_channel"."update_time" IS '更新时间';
  208 +COMMENT
  209 +ON COLUMN "public"."tk_video_channel"."remark" IS '备注';
  210 +COMMENT
  211 +ON COLUMN "public"."tk_video_channel"."channel_id" IS '视频通道编码ID';
  212 +COMMENT
  213 +ON COLUMN "public"."tk_video_channel"."stream_id" IS '流唯一编号,存在表示正在直播';
  214 +COMMENT
  215 +ON COLUMN "public"."tk_video_channel"."device_id" IS '通道所属设备ID';
  216 +COMMENT
  217 +ON TABLE "public"."tk_video_channel" IS '摄像头通道信息表(字段来源于GBT28181)';
  218 +
  219 +-- ----------------------------
  220 +-- Primary Key structure for table tk_video_channel
  221 +-- ----------------------------
  222 +ALTER TABLE "public"."tk_video_channel"
  223 + ADD CONSTRAINT "tk_video_channel_pkey" PRIMARY KEY ("id");
  224 +
  225 +DROP TABLE IF EXISTS "public"."tk_device_access_information";
  226 +CREATE TABLE "public"."tk_device_access_information"
  227 +(
  228 + "id" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
  229 + "intranet_ip" varchar(100) COLLATE "pg_catalog"."default",
  230 + "intranet_port" varchar(100) COLLATE "pg_catalog"."default",
  231 + "outer_net_ip" varchar(100) COLLATE "pg_catalog"."default",
  232 + "outer_net_port" varchar(100) COLLATE "pg_catalog"."default",
  233 + "device_agreement" varchar(100) COLLATE "pg_catalog"."default",
  234 + "sip_extend" varchar COLLATE "pg_catalog"."default",
  235 + "creator" char(64) COLLATE "pg_catalog"."default",
  236 + "create_time" timestamp(0),
  237 + "updater" char(64) COLLATE "pg_catalog"."default",
  238 + "update_time" timestamp(0),
  239 + "tenant_id" varchar(64) COLLATE "pg_catalog"."default"
  240 +)
  241 +;
  242 +COMMENT
  243 +ON COLUMN "public"."tk_device_access_information"."intranet_ip" IS '内网ip';
  244 +COMMENT
  245 +ON COLUMN "public"."tk_device_access_information"."intranet_port" IS '内网端口';
  246 +COMMENT
  247 +ON COLUMN "public"."tk_device_access_information"."outer_net_ip" IS '外网IP';
  248 +COMMENT
  249 +ON COLUMN "public"."tk_device_access_information"."outer_net_port" IS '外网端口';
  250 +COMMENT
  251 +ON COLUMN "public"."tk_device_access_information"."device_agreement" IS '设备接入协议';
  252 +COMMENT
  253 +ON COLUMN "public"."tk_device_access_information"."sip_extend" IS 'sip扩展信息 serverId服务器id serverRegion服务器域 password';
  254 +COMMENT
  255 +ON COLUMN "public"."tk_device_access_information"."creator" IS '创建人';
  256 +COMMENT
  257 +ON COLUMN "public"."tk_device_access_information"."create_time" IS '创建时间';
  258 +COMMENT
  259 +ON COLUMN "public"."tk_device_access_information"."updater" IS '更新人';
  260 +COMMENT
  261 +ON COLUMN "public"."tk_device_access_information"."update_time" IS '更新时间';
  262 +COMMENT
  263 +ON COLUMN "public"."tk_device_access_information"."tenant_id" IS '租户id';
  264 +COMMENT
  265 +ON TABLE "public"."tk_device_access_information" IS '设备接入信息表';
  266 +
  267 +-- ----------------------------
  268 +-- Primary Key structure for table tk_device_access_information
  269 +-- ----------------------------
  270 +ALTER TABLE "public"."tk_device_access_information"
  271 + ADD CONSTRAINT "tk_device_access_information_pkey" PRIMARY KEY ("id");
  272 +
  273 +
  274 +INSERT INTO "public"."sys_dict_item"("id", "dict_id", "item_text", "item_value", "description", "sort", "status", "tenant_id", "creator", "create_time", "updater", "update_time") VALUES ('638bdb47-81bd-4982-bcf7-54ef6239f697', '73d5d8b1-0ac8-475e-97f7-709dd4125ccd', '个人中心', 'system:personal_center:view', NULL, 1, 1, '13814000-1dd2-11b2-8080-808080808080', '80808080-8080-8080-8080-808080808080', '2024-01-24 11:43:40.971602', NULL, NULL);
  275 +
  276 +INSERT INTO "public"."sys_dict_item"("id", "dict_id", "item_text", "item_value", "description", "sort", "status", "tenant_id", "creator", "create_time", "updater", "update_time") VALUES ('b963cf7c-bed8-4719-86a0-82d1530c2f1b', '6d53709b-d5b6-4b5e-b9e1-0a55e6c8f9f5', '设备接入信息编辑', 'api:yt:device_profile:access_information:update', NULL, 16, 1, '13814000-1dd2-11b2-8080-808080808080', '80808080-8080-8080-8080-808080808080', '2024-02-22 18:51:34.10582', NULL, NULL);
  277 +
  278 +INSERT INTO "public"."sys_dict_item"("id", "dict_id", "item_text", "item_value", "description", "sort", "status", "tenant_id", "creator", "create_time", "updater", "update_time") VALUES ('9129d75f-d9e7-4351-bf1c-1664c292c8d9', '6d53709b-d5b6-4b5e-b9e1-0a55e6c8f9f5', '设备接入信息删除', 'api:yt:device_profile:access_information:delete', NULL, 15, 1, '13814000-1dd2-11b2-8080-808080808080', '80808080-8080-8080-8080-808080808080', '2024-02-21 14:52:52.907144', NULL, NULL);
  279 +
  280 +INSERT INTO "public"."sys_dict_item"("id", "dict_id", "item_text", "item_value", "description", "sort", "status", "tenant_id", "creator", "create_time", "updater", "update_time") VALUES ('b17b1559-8f42-44d5-92d4-122e9f70cb50', 'c5183b83-eb2a-4111-959b-5c7c7e668d94', '设备接入信息', 'deviceManager:deviceAccess:list', NULL, 1, 1, '13814000-1dd2-11b2-8080-808080808080', '80808080-8080-8080-8080-808080808080', '2024-02-19 10:29:24.048745', '80808080-8080-8080-8080-808080808080', '2024-02-19 10:29:39.684479');
  281 +
  282 +INSERT INTO "public"."sys_dict_item"("id", "dict_id", "item_text", "item_value", "description", "sort", "status", "tenant_id", "creator", "create_time", "updater", "update_time") VALUES ('c8f7c6d5-4cd2-4c82-87cf-c6d2facecf77', '6d53709b-d5b6-4b5e-b9e1-0a55e6c8f9f5', '设备接入信息新增', 'api:yt:device_profile:access_information:post', NULL, 14, 1, '13814000-1dd2-11b2-8080-808080808080', '80808080-8080-8080-8080-808080808080', '2024-02-21 14:52:12.462541', '80808080-8080-8080-8080-808080808080', '2024-02-21 14:53:15.150577');
  283 +
  284 +INSERT INTO "public"."sys_dict_item"("id", "dict_id", "item_text", "item_value", "description", "sort", "status", "tenant_id", "creator", "create_time", "updater", "update_time") VALUES ('f8c06892-f637-4c31-aac4-f935728cf625', 'c5183b83-eb2a-4111-959b-5c7c7e668d94', '个人中心', 'system:personal_center:view', NULL, 1, 1, '13814000-1dd2-11b2-8080-808080808080', '80808080-8080-8080-8080-808080808080', '2024-01-24 11:41:18.521978', NULL, NULL);
  285 +
  286 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('00fccddb-cbdc-4abd-9a1f-1c4f049e6ba2', '38b9a21a-aaf5-42fc-a85f-4381bca3303c', NULL, 'SYSADMIN', 'api:yt:device_profile:access_information:delete', '80808080-8080-8080-8080-808080808080', '2024-02-19 16:44:56.138043', '80808080-8080-8080-8080-808080808080', '2024-02-21 11:28:44.753107', '删除', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"删除","menuType":"2","status":"0"}', 4);
  287 +
  288 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('38b9a21a-aaf5-42fc-a85f-4381bca3303c', '885ef223-94b1-4b39-89a8-94584183c0be', '/device/deviceaccess', 'SYSADMIN', 'deviceManager:deviceAccess:list', '80808080-8080-8080-8080-808080808080', '2024-02-19 10:24:45.968051', '80808080-8080-8080-8080-808080808080', '2024-02-20 15:42:57.492133', '设备接入信息', '13814000-1dd2-11b2-8080-808080808080', '/device/deviceaccess/index', NULL, NULL, NULL, '{"icon":"ant-design:branches-outlined","title":"设备接入信息","isLink":false,"menuType":"1","ignoreKeepAlive":false,"hideMenu":false,"status":"0"}', 5);
  289 +
  290 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('44f65518-2d9e-4fd5-aa3f-8baf816d912f', '38b9a21a-aaf5-42fc-a85f-4381bca3303c', NULL, 'SYSADMIN', 'api:yt:device_profile:access_information:get', '80808080-8080-8080-8080-808080808080', '2024-02-19 16:43:38.877863', '80808080-8080-8080-8080-808080808080', '2024-02-21 11:28:31.362367', '详情', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"详情","menuType":"2","status":"0"}', 3);
  291 +
  292 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('68da3a6d-75e8-49f7-a27f-a4ff6bf822db', '815b9cb0-dbdd-4e57-a1c5-2820741d8e5f', NULL, 'SYSADMIN', 'api:yt:video:control:play', '80808080-8080-8080-8080-808080808080', '2024-02-21 17:51:53.607779', '80808080-8080-8080-8080-808080808080', '2024-02-21 17:52:07.960339', '视频点播', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"视频点播","menuType":"2","status":"0"}', 12);
  293 +
  294 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('68da3a6d-75e8-49f7-a27f-a4ff6bf832db', '815b9cb0-dbdd-4e57-a1c5-2820741d8e5f', NULL, 'SYSADMIN', 'api:yt:video:control:channel', '80808080-8080-8080-8080-808080808080', '2024-02-21 17:51:53.607779', '80808080-8080-8080-8080-808080808080', '2024-02-21 17:52:07.960339', '视频通道同步', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"视频通道同步","menuType":"2","status":"0"}', 12);
  295 +
  296 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('68da3a6d-75e8-49f7-a27f-a4ff6bf833db', '815b9cb0-dbdd-4e57-a1c5-2820741d8e5f', NULL, 'SYSADMIN', 'api:yt:video:control:control', '80808080-8080-8080-8080-808080808080', '2024-02-21 17:51:53.607779', '80808080-8080-8080-8080-808080808080', '2024-02-21 17:52:07.960339', '摄像头控制', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"摄像头控制","menuType":"2","status":"0"}', 12);
  297 +
  298 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('966b3dbb-37c7-4cf0-95d1-7d66511f0a3a', '815b9cb0-dbdd-4e57-a1c5-2820741d8e5f', NULL, 'SYSADMIN', 'api:yt:video:control:stop', '80808080-8080-8080-8080-808080808080', '2024-02-21 17:52:44.156915', NULL, NULL, '视频停止点播', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"视频停止点播","menuType":"2","status":"0"}', 13);
  299 +
  300 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('b47acbb6-ef7f-4c8a-9ab7-17098b5f3b63', '38b9a21a-aaf5-42fc-a85f-4381bca3303c', NULL, 'SYSADMIN', 'api:yt:device_profile:access_information:post', '80808080-8080-8080-8080-808080808080', '2024-02-19 16:42:38.723093', '80808080-8080-8080-8080-808080808080', '2024-02-21 11:27:57.643463', '新增', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"新增","menuType":"2","status":"0"}', 1);
  301 +
  302 +INSERT INTO "public"."sys_menu"("id", "parent_id", "path", "type", "permission", "creator", "create_time", "updater", "update_time", "name", "tenant_id", "component", "redirect", "alias", "case_sensitive", "meta", "sort") VALUES ('dac55d7f-4359-4a4f-99f5-4cb728c1efee', '38b9a21a-aaf5-42fc-a85f-4381bca3303c', NULL, 'SYSADMIN', 'api:yt:device_profile:access_information:update', '80808080-8080-8080-8080-808080808080', '2024-02-21 15:34:25.935524', NULL, NULL, '编辑', '13814000-1dd2-11b2-8080-808080808080', NULL, NULL, NULL, NULL, '{"title":"编辑","menuType":"2","status":"0"}', 2);
  303 +
  304 +UPDATE "public"."tk_data_view_interface" SET "interface_name" = '设备历史轨迹(适合设备历史轨迹地图)', "request_content_type" = 0, "request_origin_url" = 'localhost', "request_http_type" = 'GET', "request_url" = '/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries{?agg,endTs,interval,keys,limit,orderBy,startTs,endTs,useStrictDataTypes}', "request_params" = '{"requestSQLContent":{},"Params":[{"key":"scope","value":"entityType","mores":null,"editDisabled":false,"required":true},{"key":"date_range","value":"startTs,endTs","mores":true,"editDisabled":true,"required":false},{"key":"deviceProfileId,organizationId,entityId,keys","value":"","mores":null,"editDisabled":true,"required":false}],"Header":[{"key":"ContentType","value":"none","required":false}]}', "state" = 1, "creator" = '80808080-8080-8080-8080-808080808080', "create_time" = '2023-12-26 14:36:44.891373', "updater" = '80808080-8080-8080-8080-808080808080', "update_time" = '2024-01-03 14:48:27.96855', "tenant_id" = '13814000-1dd2-11b2-8080-808080808080', "remark" = NULL, "interface_type" = 'SYSTEM', "filter" = 'const longKey = ''longitude''
  305 +const latKey = ''latitude''
  306 +const allKeys = Object.keys(res)
  307 +
  308 +let list = []
  309 +
  310 +//不是结构体
  311 +if (allKeys.includes(longKey)) {
  312 + res[longKey].forEach((longItem, longIndex) => {
  313 + list.push([
  314 + Number(longItem.value),
  315 + Number(res[latKey][longIndex].value),
  316 + ])
  317 + })
  318 +} else {
  319 + //为结构体
  320 + const values = Object.values(res)
  321 + list = values[0].reduce((acc, curr)=> {
  322 + const serializeValue = JSON.parse(curr.value)
  323 + const {
  324 + longitude, latitude
  325 + } = serializeValue
  326 + acc.push([longitude, latitude])
  327 + return acc
  328 + }, [])
  329 +}
  330 +return list.reverse()' WHERE "id" = 'f65d5637-3e60-48d7-b3cc-6791fb3883fd';
  331 +
  332 +INSERT INTO "public"."tk_device_access_information"("id", "intranet_ip", "intranet_port", "outer_net_ip", "outer_net_port", "device_agreement", "sip_extend", "creator", "create_time", "updater", "update_time", "tenant_id") VALUES ('ff1edbc3-0cfd-4263-ae50-cf9f11f9a88d', '127.0.0.1', '5060', '127.0.0.1', '5060', 'GBT28181', '{"serverId":"51010700599000000001","serverDomain":"5101070059","serverPassword":"61332286"}', '80808080-8080-8080-8080-808080808080 ', '2024-02-29 15:29:54', NULL, NULL, '13814000-1dd2-11b2-8080-808080808080');
@@ -74,7 +74,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -74,7 +74,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
74 74
75 //Thingskit function 75 //Thingskit function
76 public static final String CODE_BASED_LOGIN_ENTRY_POINT = "/api/yt/auth/code/login"; 76 public static final String CODE_BASED_LOGIN_ENTRY_POINT = "/api/yt/auth/code/login";
77 - public static final String[] YT_NOT_AUTH_API = new String[]{"/api/yt/auth/code/login","/api/yt/third/bind","/api/yt/third/login/*","/api/yt/third/login/id/*", "/api/yt/third/authorize","/api/yt/platform/get","/api/yt/app_design/get", "/api/yt/noauth/**"}; 77 + public static final String[] YT_NOT_AUTH_API = new String[]{"/api/yt/auth/code/login","/api/yt/third/bind","/api/yt/third/login/*","/api/yt/third/login/id/*", "/api/yt/third/authorize","/api/yt/platform/get","/api/yt/app_design/get", "/api/yt/noauth/**","/api/index/hook/**"};
78 78
79 public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public"; 79 public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
80 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; 80 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
@@ -82,7 +82,7 @@ public class ThingsKitExceptionHandler { @@ -82,7 +82,7 @@ public class ThingsKitExceptionHandler {
82 SecurityUser currentUser = getCurrentUser(); 82 SecurityUser currentUser = getCurrentUser();
83 83
84 Asset entity = new Asset(); 84 Asset entity = new Asset();
85 - entity.setName(e.getMessage()); 85 + entity.setName(e.getClass().getName());
86 86
87 // 请求相关信息 87 // 请求相关信息
88 SysLogOperateDTO additionalInfo = new SysLogOperateDTO(); 88 SysLogOperateDTO additionalInfo = new SysLogOperateDTO();
1 package org.thingsboard.server.controller.yunteng; 1 package org.thingsboard.server.controller.yunteng;
2 2
  3 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.PAGE;
  4 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.PAGE_SIZE;
  5 +
3 import io.swagger.annotations.Api; 6 import io.swagger.annotations.Api;
4 import io.swagger.annotations.ApiOperation; 7 import io.swagger.annotations.ApiOperation;
5 import io.swagger.annotations.ApiParam; 8 import io.swagger.annotations.ApiParam;
  9 +import java.util.HashMap;
  10 +import java.util.List;
  11 +import java.util.concurrent.ExecutionException;
6 import lombok.RequiredArgsConstructor; 12 import lombok.RequiredArgsConstructor;
7 import org.springframework.http.HttpStatus; 13 import org.springframework.http.HttpStatus;
8 import org.springframework.http.ResponseEntity; 14 import org.springframework.http.ResponseEntity;
@@ -23,91 +29,87 @@ import org.thingsboard.server.dao.exception.DataValidationException; @@ -23,91 +29,87 @@ import org.thingsboard.server.dao.exception.DataValidationException;
23 import org.thingsboard.server.dao.yunteng.service.HomePageService; 29 import org.thingsboard.server.dao.yunteng.service.HomePageService;
24 import org.thingsboard.server.service.security.model.SecurityUser; 30 import org.thingsboard.server.service.security.model.SecurityUser;
25 31
26 -import java.util.HashMap;  
27 -import java.util.List;  
28 -import java.util.concurrent.ExecutionException;  
29 -  
30 -import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.PAGE;  
31 -import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.PAGE_SIZE;  
32 -  
33 @RequestMapping("api/yt/homepage") 32 @RequestMapping("api/yt/homepage")
34 @Api(tags = {"首页"}) 33 @Api(tags = {"首页"})
35 @RequiredArgsConstructor 34 @RequiredArgsConstructor
36 @RestController 35 @RestController
37 public class HomePageController extends BaseController { 36 public class HomePageController extends BaseController {
38 37
39 - private final HomePageService homePageService; 38 + private final HomePageService homePageService;
40 39
41 - @GetMapping("left/top")  
42 - @ApiOperation(value = "获取左侧顶部信息")  
43 - public HomePageLeftTopDTO getLeftTopInfo()  
44 - throws ThingsboardException, ExecutionException, InterruptedException {  
45 - return homePageService.getHomePageLeftTopInfo(  
46 - getCurrentUser().isPtSysadmin(),  
47 - getCurrentUser().isPtAdmin(),  
48 - getCurrentUser().isPtTenantAdmin(),  
49 - getCurrentUser().getCurrentTenantId(),  
50 - getCurrentUser().getCurrentUserId());  
51 - } 40 + @GetMapping("left/top")
  41 + @ApiOperation(value = "获取左侧顶部信息")
  42 + public HomePageLeftTopDTO getLeftTopInfo()
  43 + throws ThingsboardException, ExecutionException, InterruptedException {
  44 + return homePageService.getHomePageLeftTopInfo(
  45 + getCurrentUser().isPtSysadmin(),
  46 + getCurrentUser().isPtAdmin(),
  47 + getCurrentUser().isPtTenantAdmin(),
  48 + getCurrentUser().getCurrentTenantId(),
  49 + getCurrentUser().getCurrentUserId());
  50 + }
52 51
53 - @GetMapping("right/overdue")  
54 - @ApiOperation(value = "获取右侧过期租户信息")  
55 - @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{})")  
56 - public ResponseEntity<TkPageData<TenantDTO>> getRightTopInfo(  
57 - @RequestParam(PAGE) int page, @RequestParam(PAGE_SIZE) int pageSize) {  
58 - HashMap<String, Object> queryMap = new HashMap<>();  
59 - queryMap.put(PAGE_SIZE, pageSize);  
60 - queryMap.put(PAGE, page);  
61 - return ResponseEntity.ok(homePageService.getHomePageRightInfo(queryMap));  
62 - } 52 + @GetMapping("right/overdue")
  53 + @ApiOperation(value = "获取右侧过期租户信息")
  54 + @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{})")
  55 + public ResponseEntity<TkPageData<TenantDTO>> getRightTopInfo(
  56 + @RequestParam(PAGE) int page, @RequestParam(PAGE_SIZE) int pageSize) {
  57 + HashMap<String, Object> queryMap = new HashMap<>();
  58 + queryMap.put(PAGE_SIZE, pageSize);
  59 + queryMap.put(PAGE, page);
  60 + return ResponseEntity.ok(homePageService.getHomePageRightInfo(queryMap));
  61 + }
63 62
64 - @GetMapping("right/top10")  
65 - @ApiOperation(value = "获取右侧Top10")  
66 - @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{})")  
67 - public DeferredResult<List<TenantTransportMessageDTO>> getTop10() {  
68 - return homePageService.getTop10();  
69 - } 63 + @GetMapping("right/top10")
  64 + @ApiOperation(value = "获取右侧Top10")
  65 + @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{})")
  66 + public DeferredResult<List<TenantTransportMessageDTO>> getTop10() {
  67 + return homePageService.getTop10();
  68 + }
70 69
71 - @GetMapping("left/bottom")  
72 - @ApiOperation(value = "获取左侧底部信息")  
73 - @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','CUSTOMER_USER'},{})")  
74 - public DeferredResult<List<TkTsValue>> getLeftBottomInfo(  
75 - @RequestParam(value = "startTs") long startTs,  
76 - @RequestParam("endTs") long endTs,  
77 - @RequestParam("interval") long interval,  
78 - @RequestParam("trend") TrendType trend)  
79 - throws ThingsboardException {  
80 - String customerId;  
81 - if (getCurrentUser().isPtAdmin()) {  
82 - customerId = getCurrentUser().getCurrentTenantId();  
83 - } else {  
84 - customerId = getCurrentUser().getCustomerId().getId().toString();  
85 - }  
86 - boolean isCustomer = getCurrentUser().isCustomerUser();  
87 - if (TrendType.CUSTOMER_MESSAGE_STATISTICAL == trend  
88 - || TrendType.CUSTOMER_ALARM_STATISTICAL == trend) {  
89 - if (!isCustomer) {  
90 - throw new DataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());  
91 - }  
92 - }  
93 - if (TrendType.TENANT_TREND == trend || TrendType.CUSTOMER_TREND == trend) {  
94 - if (isCustomer) {  
95 - throw new DataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());  
96 - }  
97 - }  
98 - return homePageService.getHomePageLeftBottomInfo(  
99 - customerId, startTs, endTs, interval, trend, isCustomer); 70 + @GetMapping("left/bottom")
  71 + @ApiOperation(value = "获取左侧底部历史数据")
  72 + @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','CUSTOMER_USER'},{})")
  73 + public DeferredResult<List<TkTsValue>> getLeftBottomInfo(
  74 + @RequestParam(value = "startTs") long startTs,
  75 + @RequestParam("endTs") long endTs,
  76 + @RequestParam("interval") long interval,
  77 + @RequestParam("trend") TrendType trend)
  78 + throws ThingsboardException {
  79 + SecurityUser currentUser = getCurrentUser();
  80 + boolean isCustomer = currentUser.isCustomerUser();
  81 + if (TrendType.CUSTOMER_MESSAGE_STATISTICAL == trend
  82 + || TrendType.CUSTOMER_ALARM_STATISTICAL == trend) {
  83 + if (!isCustomer) {
  84 + throw new DataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  85 + }
  86 + }
  87 + if (TrendType.TENANT_TREND == trend || TrendType.CUSTOMER_TREND == trend) {
  88 + if (isCustomer) {
  89 + throw new DataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  90 + }
100 } 91 }
  92 + return homePageService.getHomePageLeftBottomInfo(
  93 + currentUser.getTenantId(), currentUser.getCustomerId(), startTs, endTs, interval, trend);
  94 + }
101 95
102 - @GetMapping("app")  
103 - @ApiOperation(value = "小程序首页统计信息")  
104 - public ResponseEntity<HomePageAppDTO> appStatistics(@ApiParam(value = "只取告警数据") @RequestParam("login") Boolean login)  
105 - throws ThingsboardException{  
106 - SecurityUser user = getCurrentUser();  
107 - if(user == null){  
108 - return ResponseEntity.status(HttpStatus.FORBIDDEN).build();  
109 - }  
110 - HomePageAppDTO app = homePageService.app(login,user.isPtSysadmin(),user.isPtAdmin(),user.isPtTenantAdmin(),user.getTenantId(),user.getCustomerId());  
111 - return ResponseEntity.ok(app); 96 + @GetMapping("app")
  97 + @ApiOperation(value = "小程序首页统计信息")
  98 + public ResponseEntity<HomePageAppDTO> appStatistics(
  99 + @ApiParam(value = "只取告警数据") @RequestParam("login") Boolean login)
  100 + throws ThingsboardException {
  101 + SecurityUser user = getCurrentUser();
  102 + if (user == null) {
  103 + return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
112 } 104 }
  105 + HomePageAppDTO app =
  106 + homePageService.app(
  107 + login,
  108 + user.isPtSysadmin(),
  109 + user.isPtAdmin(),
  110 + user.isPtTenantAdmin(),
  111 + user.getTenantId(),
  112 + user.getCustomerId());
  113 + return ResponseEntity.ok(app);
  114 + }
113 } 115 }
@@ -22,19 +22,18 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -22,19 +22,18 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
22 import org.thingsboard.server.common.data.yunteng.common.AddGroup; 22 import org.thingsboard.server.common.data.yunteng.common.AddGroup;
23 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; 23 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup;
24 import org.thingsboard.server.common.data.yunteng.common.UpdateGroup; 24 import org.thingsboard.server.common.data.yunteng.common.UpdateGroup;
  25 +import org.thingsboard.server.common.data.yunteng.core.exception.ThingsKitException;
25 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; 26 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
26 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; 27 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
27 import org.thingsboard.server.common.data.yunteng.dto.*; 28 import org.thingsboard.server.common.data.yunteng.dto.*;
28 import org.thingsboard.server.common.data.yunteng.dto.thingsmodel.ImportThingsModelDTO; 29 import org.thingsboard.server.common.data.yunteng.dto.thingsmodel.ImportThingsModelDTO;
29 -import org.thingsboard.server.common.data.yunteng.enums.FunctionTypeEnum;  
30 -import org.thingsboard.server.common.data.yunteng.enums.ImportEnum;  
31 -import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;  
32 -import org.thingsboard.server.common.data.yunteng.enums.StatusEnum; 30 +import org.thingsboard.server.common.data.yunteng.enums.*;
33 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult; 31 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
34 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; 32 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
35 import org.thingsboard.server.controller.BaseController; 33 import org.thingsboard.server.controller.BaseController;
36 import org.thingsboard.server.dao.yunteng.service.ThingsModelService; 34 import org.thingsboard.server.dao.yunteng.service.ThingsModelService;
37 import org.thingsboard.server.dao.yunteng.service.TkDeviceProfileService; 35 import org.thingsboard.server.dao.yunteng.service.TkDeviceProfileService;
  36 +import org.thingsboard.server.utils.ImportModbusUtils;
38 37
39 import java.io.File; 38 import java.io.File;
40 import java.io.IOException; 39 import java.io.IOException;
@@ -42,6 +41,7 @@ import java.io.InputStream; @@ -42,6 +41,7 @@ import java.io.InputStream;
42 import java.util.HashMap; 41 import java.util.HashMap;
43 import java.util.List; 42 import java.util.List;
44 import java.util.Map; 43 import java.util.Map;
  44 +import java.util.regex.Pattern;
45 45
46 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*; 46 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*;
47 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.ORDER_TYPE; 47 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.ORDER_TYPE;
@@ -234,16 +234,221 @@ public class ThingsModelController extends BaseController { @@ -234,16 +234,221 @@ public class ThingsModelController extends BaseController {
234 return thingsModelService.saveOrUpdate(thingsModelDTO); 234 return thingsModelService.saveOrUpdate(thingsModelDTO);
235 } 235 }
236 236
  237 + @GetMapping("/download/template")
  238 + @ApiOperation("excel模板下载")
  239 + public ResponseEntity<Resource> downloadTemplate(String type) throws IOException {
  240 + String name = "template"+ File.separator;
  241 + if(StringUtils.isNotEmpty(type)&&type.equals("modbus")){
  242 + name = name +"modbusTemplates.xls";
  243 + }else{
  244 + name = name+"template.xls";
  245 + }
  246 + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(name);
  247 + if(inputStream!=null){
  248 + HttpHeaders headers = new HttpHeaders();
  249 + headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=物模型属性导入模板.xls");
  250 + return ResponseEntity.ok()
  251 + .headers(headers)
  252 + .contentType(MediaType.APPLICATION_OCTET_STREAM)
  253 + .body(new InputStreamResource(inputStream));
  254 + }else {
  255 + return null;
  256 + }
  257 + }
  258 +
  259 +
  260 + @PostMapping("/batch/get_tsl")
  261 + @ApiOperation("获取多个产品的物模型数据")
  262 + public ResponseEntity<List<BatchDeviceProfileInfoDTO>> getDeviceProfilesTSL(@RequestBody DeviceProfileTSLDTO profileTSLDTO)
  263 + throws ThingsboardException {
  264 + if(null == profileTSLDTO.getDeviceProfileIds() || null == profileTSLDTO.getFunctionTypeEnum() ||
  265 + profileTSLDTO.getDeviceProfileIds().isEmpty()){
  266 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  267 + }
  268 + return ResponseEntity.ok(thingsModelService.getDeviceProfileTSLInfoByIds(getCurrentUser().getCurrentTenantId(),
  269 + profileTSLDTO.getDeviceProfileIds(),profileTSLDTO.getFunctionTypeEnum()));
  270 + }
  271 +
237 @ApiOperation(value = "通过excel导入物模型") 272 @ApiOperation(value = "通过excel导入物模型")
238 @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN'},{'api:yt:things_model:excel_import'})") 273 @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN'},{'api:yt:things_model:excel_import'})")
239 @PostMapping("/csvImport") 274 @PostMapping("/csvImport")
240 - public ResponseResult<String> csvImport(String deviceProfileId, String categoryId, 275 + public ResponseResult<String> csvImport(String deviceProfileId, String categoryId,String type,
241 @RequestPart("file") MultipartFile file) throws Exception { 276 @RequestPart("file") MultipartFile file) throws Exception {
242 if(file.isEmpty()){ 277 if(file.isEmpty()){
243 - return null; 278 + throw new ThingsKitException(ErrorMessage.INVALID_PARAMETER);
244 } 279 }
  280 + try{
245 Workbook work = WorkbookFactory.create(file.getInputStream()); 281 Workbook work = WorkbookFactory.create(file.getInputStream());
  282 + String tenantId = getCurrentUser().getCurrentTenantId();
  283 + if(type.equals("modbus")){
  284 + return importModbus(deviceProfileId,categoryId,file,tenantId,work);
  285 + }else{
  286 + return importModel(deviceProfileId,categoryId,file,tenantId,work);
  287 + }
  288 + }catch (Exception e){
  289 + throw new TkDataValidationException(ErrorMessage.IMPORT_ERROR.getMessage());
  290 + }
  291 + }
  292 +
  293 +
  294 + ResponseResult<String> importModbus(String deviceProfileId, String categoryId,MultipartFile file,String tenantId, Workbook work) throws IOException {
  295 +
  296 + Sheet sheet = work.getSheetAt(0);
  297 + int succeed = 0;//成功数量
  298 + int failed =0;//失败数量
  299 + StringBuffer failedString = new StringBuffer();
  300 + String excel = sheet.getRow(0).getCell(2).toString();
  301 + if(!excel.equals("MODBUS_RTU操作类型*")){
  302 + throw new TkDataValidationException(ErrorMessage.IMPORT_ERROR.getMessage());
  303 + }
  304 + for(int i = 1; i<=sheet.getPhysicalNumberOfRows(); i++) {
  305 + Row row = sheet.getRow(i);
  306 + if(row!=null){
  307 + String functionName = row.getCell(0).toString();//功能名称
  308 + if(StringUtils.isEmpty(functionName)){
  309 + failed++;
  310 + failedString.append("第"+i+"条,功能名称未填;");
  311 + continue;
  312 + }
  313 + String identifier = row.getCell(1).toString();//标识符*
  314 + if(StringUtils.isEmpty(identifier)){
  315 + failed++;
  316 + failedString.append("第"+i+"条,标识符未填;");
  317 + continue;
  318 + }
  319 + Boolean identifierState = thingsModelService.getByIdentifier(tenantId,deviceProfileId,categoryId,identifier);
  320 + if(!identifierState){
  321 + failed++;
  322 + failedString.append("第"+i+"条,标识符已存在;");
  323 + continue;
  324 + }
  325 + ThingsModelDTO dto = new ThingsModelDTO();
  326 +
  327 + String extensionJson ="";
  328 + String json ="";
  329 +
  330 + if(identifier.equals("source")){
  331 + dto.setAccessMode("r");
  332 + extensionJson ="{\"writeOnly\":true}";
  333 + json ="{\"dataType\":{\"type\":\"STRING\",\"specs\":{}}}";
  334 + }else{
  335 + String operationType = row.getCell(2).toString();//MODBUS_RTU操作类型*
  336 + if(StringUtils.isEmpty(operationType)){
  337 + failed++;
  338 + failedString.append("第"+i+"条,MODBUS_RTU操作类型未填;");
  339 + continue;
  340 + }
  341 + String originalDataType = row.getCell(3).toString();//MODBUS_RTU数据类型*
  342 + if(StringUtils.isEmpty(originalDataType)){
  343 + failed++;
  344 + failedString.append("第"+i+"条,MODBUS_RTU数据类型未填;");
  345 + continue;
  346 + }
  347 + operationType =OperationTypeEnum.getName(operationType);
  348 + originalDataType = AttributeSourceDataTypeEnum.getName(originalDataType);
  349 + if(!ImportModbusUtils.operationTypeAndOriginalDataTypeStatus(operationType, originalDataType)){
  350 + failed++;
  351 + failedString.append("第"+i+"条,操作类型与数据类型不匹配;");
  352 + continue;
  353 + }
  354 +
  355 +
  356 + String registerType = row.getCell(5).toString();//寄存器类型
  357 + String registerAddress =row.getCell(6).toString();//寄存器地址
  358 + if(StringUtils.isEmpty(registerType)){
  359 + failed++;
  360 + failedString.append("第"+i+"条,寄存器类型未填;");
  361 + continue;
  362 + }
  363 + //转换校验输入的字符
  364 + if(registerType.equals("HEX")){
  365 + String hexPattern="^[0-9A-Fa-f]+$";
  366 + if(!Pattern.matches(hexPattern,registerAddress)){
  367 + failedString.append("第"+i+"条,寄存器地址格式不正确;");
  368 + continue;
  369 + }
  370 + }
  371 + if(registerType.equals("DEC")){
  372 + try{
  373 + registerAddress = Integer.toHexString(Integer.parseInt(registerAddress));
  374 + } catch (NumberFormatException e) {
  375 + failedString.append("第"+i+"条,寄存器地址格式不正确;");
  376 + continue;
  377 + }
  378 + }
  379 +
  380 + String bitMask = row.getCell(7)==null?null:row.getCell(7).toString();//比特位*
  381 + if(originalDataType.equals("位")&&StringUtils.isEmpty(bitMask)){
  382 + failed++;
  383 + failedString.append("第"+i+"条,比特位置(数据类型为位时必填);");
  384 + continue;
  385 + }
  386 + if(StringUtils.isEmpty(bitMask)){
  387 + bitMask="7";
  388 + }
  389 +
  390 + String scaling = row.getCell(8)==null?"1":row.getCell(8).toString();//缩放因子
  391 + String registerCount = row.getCell(9)==null?"":row.getCell(9).toString();//寄存器个数
  392 +
  393 + if(originalDataType.equals("字符串")&&StringUtils.isEmpty(registerCount)){
  394 + failed++;
  395 + failedString.append("第"+i+"条,寄存器个数(数据类型为字符串时必填);");
  396 + continue;
  397 + }
  398 +
  399 +
  400 + String unit ="";
  401 + String valueRange ="";
  402 + String dataType = ImportModbusUtils.getOriginalDataTypeIsDateType(originalDataType);
  403 + String cell4 = row.getCell(4)==null?null:row.getCell(4).toString();//单位
  404 + if(cell4!=null){
  405 + String[] dws = cell4.split("/");
  406 + unit = "\"unit\":\""+dws[1]+"\",\"unitName\":\""+cell4+"\"";
  407 + }
  408 + if(unit.equals("")){
  409 + valueRange = ImportModbusUtils.getOriginalDataTypeIsValueRange(originalDataType);
  410 + }else{
  411 + valueRange = ","+ImportModbusUtils.getOriginalDataTypeIsValueRange(originalDataType);
  412 + }
  413 + json ="{\"dataType\":{\"type\":\""+dataType+"\",\"specs\":{"+unit+valueRange+"}}}";
  414 + extensionJson =
  415 + "{\"writeOnly\":"+ImportModbusUtils.getOperationTypeIsWriteOnly(operationType)+
  416 + ",\"bitMask\":"+bitMask+",\"operationType\":\""+operationType+"\",\"originalDataType\":\""+originalDataType+
  417 + "\",\"registerAddress\":\""+registerAddress+"\",\"scaling\":"+scaling+"}";
  418 + if(!registerCount.equals("")){
  419 + extensionJson = extensionJson+",\"registerCount\":"+registerCount;
  420 + }
  421 + dto.setAccessMode(ImportModbusUtils.getOperationTypeIsRw(operationType));
  422 + }
  423 +
  424 + dto.setDeviceProfileId(deviceProfileId);
  425 + dto.setCategoryId(categoryId);
  426 + dto.setFunctionType(FunctionTypeEnum.properties);
  427 + dto.setFunctionName(functionName);
  428 + dto.setIdentifier(identifier);
  429 + JsonNode functionJson = JacksonUtil.toJsonNode(json);
  430 + JsonNode extensionDesc = JacksonUtil.toJsonNode(extensionJson);
  431 + dto.setFunctionJson(functionJson);
  432 + dto.setExtensionDesc(extensionDesc);
  433 + dto.setRemark(row.getCell(10)==null?null:row.getCell(10).toString());
  434 + dto.setTenantId(tenantId);
  435 + if(dto.getCategoryId()!=null){
  436 + thingsModelService.categorySaveOrUpdate(dto);
  437 + }else{
  438 + thingsModelService.saveOrUpdate(dto);
  439 + }
  440 + succeed++;
  441 + }
  442 + }
  443 + return ResponseResult.success("操作成功,导入"+succeed+"条;失败"+failed+"条;失败原因:"+failedString);
  444 + }
  445 +
  446 + ResponseResult<String> importModel(String deviceProfileId, String categoryId,MultipartFile file,String tenantId, Workbook work) throws IOException {
246 Sheet sheet = work.getSheetAt(0); 447 Sheet sheet = work.getSheetAt(0);
  448 + String excel = sheet.getRow(0).getCell(2).toString();
  449 + if(!excel.equals("数据类型*")){
  450 + return ResponseResult.failed(500,"操作失败,请使用下载模板excel重新导入");
  451 + }
247 int succeed = 0;//成功数量 452 int succeed = 0;//成功数量
248 int failed =0;//失败数量 453 int failed =0;//失败数量
249 StringBuffer failedString = new StringBuffer(); 454 StringBuffer failedString = new StringBuffer();
@@ -253,41 +458,39 @@ public class ThingsModelController extends BaseController { @@ -253,41 +458,39 @@ public class ThingsModelController extends BaseController {
253 String functionName = row.getCell(0).toString();//功能名称 458 String functionName = row.getCell(0).toString();//功能名称
254 if(StringUtils.isEmpty(functionName)){ 459 if(StringUtils.isEmpty(functionName)){
255 failed++; 460 failed++;
256 - failedString.append("第"+i+"条,功能名称未填\r\n"); 461 + failedString.append("第"+i+"条,功能名称未填");
257 continue; 462 continue;
258 } 463 }
259 String identifier = row.getCell(1).toString();//标识符* 464 String identifier = row.getCell(1).toString();//标识符*
260 if(StringUtils.isEmpty(identifier)){ 465 if(StringUtils.isEmpty(identifier)){
261 failed++; 466 failed++;
262 - failedString.append("第"+i+"条,标识符未填\r\n"); 467 + failedString.append("第"+i+"条,标识符未填");
263 continue; 468 continue;
264 } 469 }
265 String dataType = row.getCell(2).toString();//数据类型* 470 String dataType = row.getCell(2).toString();//数据类型*
266 if(StringUtils.isEmpty(dataType)){ 471 if(StringUtils.isEmpty(dataType)){
267 failed++; 472 failed++;
268 - failedString.append("第"+i+"条,数据类型未填\r\n"); 473 + failedString.append("第"+i+"条,数据类型未填");
269 continue; 474 continue;
270 } 475 }
271 String accessMode = row.getCell(7).toString();//读写类型* 476 String accessMode = row.getCell(7).toString();//读写类型*
272 if(StringUtils.isEmpty(accessMode)){ 477 if(StringUtils.isEmpty(accessMode)){
273 failed++; 478 failed++;
274 - failedString.append("第"+i+"条,读写类型未填\r\n"); 479 + failedString.append("第"+i+"条,读写类型未填");
275 continue; 480 continue;
276 } 481 }
277 Cell dataJons = row.getCell(9);//Josn对象 482 Cell dataJons = row.getCell(9);//Josn对象
278 if(dataType.equals("struct(结构体)")){ 483 if(dataType.equals("struct(结构体)")){
279 if(StringUtils.isEmpty(dataJons.toString())){ 484 if(StringUtils.isEmpty(dataJons.toString())){
280 failed++; 485 failed++;
281 - failedString.append("第"+i+"条,如果数据类型选为结构体则必填Json对象,请填入\r\n"); 486 + failedString.append("第"+i+"条,如果数据类型选为结构体则必填Json对象,请填入");
282 continue; 487 continue;
283 } 488 }
284 } 489 }
285 -  
286 - String tenantId = getCurrentUser().getCurrentTenantId();  
287 Boolean identifierState = thingsModelService.getByIdentifier(tenantId,deviceProfileId,categoryId,identifier); 490 Boolean identifierState = thingsModelService.getByIdentifier(tenantId,deviceProfileId,categoryId,identifier);
288 if(!identifierState){ 491 if(!identifierState){
289 failed++; 492 failed++;
290 - failedString.append("第"+i+"条,标识符已存在\r\n"); 493 + failedString.append("第"+i+"条,标识符已存在");
291 continue; 494 continue;
292 } 495 }
293 ThingsModelDTO dto = new ThingsModelDTO(); 496 ThingsModelDTO dto = new ThingsModelDTO();
@@ -349,32 +552,4 @@ public class ThingsModelController extends BaseController { @@ -349,32 +552,4 @@ public class ThingsModelController extends BaseController {
349 } 552 }
350 return ResponseResult.success("操作成功,导入"+succeed+"条;失败"+failed+"条;失败原因:"+failedString.toString()); 553 return ResponseResult.success("操作成功,导入"+succeed+"条;失败"+failed+"条;失败原因:"+failedString.toString());
351 } 554 }
352 -  
353 - @GetMapping("/downloadTemplate")  
354 - @ApiOperation("excel模板下载")  
355 - public ResponseEntity<Resource> downloadTemplate() throws IOException {  
356 - InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template"+ File.separator +"template.xls");  
357 - if(inputStream!=null){  
358 - HttpHeaders headers = new HttpHeaders();  
359 - headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=物模型属性导入模板.xls");  
360 - return ResponseEntity.ok()  
361 - .headers(headers)  
362 - .contentType(MediaType.APPLICATION_OCTET_STREAM)  
363 - .body(new InputStreamResource(inputStream));  
364 - }else {  
365 - return null;  
366 - }  
367 - }  
368 -  
369 - @PostMapping("/batch/get_tsl")  
370 - @ApiOperation("获取多个产品的物模型数据")  
371 - public ResponseEntity<List<BatchDeviceProfileInfoDTO>> getDeviceProfilesTSL(@RequestBody DeviceProfileTSLDTO profileTSLDTO)  
372 - throws ThingsboardException {  
373 - if(null == profileTSLDTO.getDeviceProfileIds() || null == profileTSLDTO.getFunctionTypeEnum() ||  
374 - profileTSLDTO.getDeviceProfileIds().isEmpty()){  
375 - throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());  
376 - }  
377 - return ResponseEntity.ok(thingsModelService.getDeviceProfileTSLInfoByIds(getCurrentUser().getCurrentTenantId(),  
378 - profileTSLDTO.getDeviceProfileIds(),profileTSLDTO.getFunctionTypeEnum()));  
379 - }  
380 } 555 }
@@ -5,7 +5,6 @@ import io.swagger.annotations.ApiOperation; @@ -5,7 +5,6 @@ import io.swagger.annotations.ApiOperation;
5 import io.swagger.annotations.ApiParam; 5 import io.swagger.annotations.ApiParam;
6 import lombok.RequiredArgsConstructor; 6 import lombok.RequiredArgsConstructor;
7 import org.springframework.http.ResponseEntity; 7 import org.springframework.http.ResponseEntity;
8 -import org.springframework.security.access.prepost.PreAuthorize;  
9 import org.springframework.validation.annotation.Validated; 8 import org.springframework.validation.annotation.Validated;
10 import org.springframework.web.bind.annotation.*; 9 import org.springframework.web.bind.annotation.*;
11 import org.thingsboard.server.common.data.exception.ThingsboardException; 10 import org.thingsboard.server.common.data.exception.ThingsboardException;
@@ -65,7 +64,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -65,7 +64,6 @@ public class TkDataViewInterfaceController extends BaseController {
65 64
66 @PostMapping 65 @PostMapping
67 @ApiOperation("新增") 66 @ApiOperation("新增")
68 - @PreAuthorize("@check.checkPermissions({},{})")  
69 public ResponseEntity<TkDataViewInterfaceDTO> save( 67 public ResponseEntity<TkDataViewInterfaceDTO> save(
70 @Validated({AddGroup.class}) @RequestBody TkDataViewInterfaceDTO tkDataViewInterfaceDTO) 68 @Validated({AddGroup.class}) @RequestBody TkDataViewInterfaceDTO tkDataViewInterfaceDTO)
71 throws ThingsboardException { 69 throws ThingsboardException {
@@ -77,7 +75,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -77,7 +75,6 @@ public class TkDataViewInterfaceController extends BaseController {
77 75
78 @PutMapping 76 @PutMapping
79 @ApiOperation("修改") 77 @ApiOperation("修改")
80 - @PreAuthorize("@check.checkPermissions({},{})")  
81 public ResponseEntity<TkDataViewInterfaceDTO> update( 78 public ResponseEntity<TkDataViewInterfaceDTO> update(
82 @Validated({UpdateGroup.class}) @RequestBody TkDataViewInterfaceDTO TkDataViewInterfaceDTO) 79 @Validated({UpdateGroup.class}) @RequestBody TkDataViewInterfaceDTO TkDataViewInterfaceDTO)
83 throws ThingsboardException { 80 throws ThingsboardException {
@@ -88,7 +85,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -88,7 +85,6 @@ public class TkDataViewInterfaceController extends BaseController {
88 85
89 @DeleteMapping 86 @DeleteMapping
90 @ApiOperation("删除") 87 @ApiOperation("删除")
91 - @PreAuthorize("@check.checkPermissions({},{})")  
92 public ResponseEntity<Boolean> delete( 88 public ResponseEntity<Boolean> delete(
93 @Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO) 89 @Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO)
94 throws ThingsboardException { 90 throws ThingsboardException {
@@ -98,7 +94,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -98,7 +94,6 @@ public class TkDataViewInterfaceController extends BaseController {
98 94
99 @GetMapping("/publish/{id}") 95 @GetMapping("/publish/{id}")
100 @ApiOperation("发布") 96 @ApiOperation("发布")
101 - @PreAuthorize("@check.checkPermissions({},{})")  
102 public ResponseEntity<Boolean> publishInterface(@PathVariable("id") String id) 97 public ResponseEntity<Boolean> publishInterface(@PathVariable("id") String id)
103 throws ThingsboardException { 98 throws ThingsboardException {
104 return ResponseEntity.ok( 99 return ResponseEntity.ok(
@@ -107,7 +102,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -107,7 +102,6 @@ public class TkDataViewInterfaceController extends BaseController {
107 102
108 @GetMapping("/cancel_publish/{id}") 103 @GetMapping("/cancel_publish/{id}")
109 @ApiOperation("取消发布") 104 @ApiOperation("取消发布")
110 - @PreAuthorize("@check.checkPermissions({},{})")  
111 public ResponseEntity<Boolean> cancelPublishInterface(@PathVariable("id") String id) 105 public ResponseEntity<Boolean> cancelPublishInterface(@PathVariable("id") String id)
112 throws ThingsboardException { 106 throws ThingsboardException {
113 return ResponseEntity.ok( 107 return ResponseEntity.ok(
@@ -116,7 +110,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -116,7 +110,6 @@ public class TkDataViewInterfaceController extends BaseController {
116 } 110 }
117 111
118 @ApiOperation("根据接口ID,获得接口详情") 112 @ApiOperation("根据接口ID,获得接口详情")
119 - @PreAuthorize("@check.checkPermissions({},{})")  
120 @RequestMapping( 113 @RequestMapping(
121 value = "/get_interface_details", 114 value = "/get_interface_details",
122 params = {"ids"}, 115 params = {"ids"},
@@ -130,7 +123,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -130,7 +123,6 @@ public class TkDataViewInterfaceController extends BaseController {
130 123
131 @GetMapping("/filter_by_interface_type/{type}") 124 @GetMapping("/filter_by_interface_type/{type}")
132 @ApiOperation("根据接口类型过滤数据") 125 @ApiOperation("根据接口类型过滤数据")
133 - @PreAuthorize("@check.checkPermissions({},{})")  
134 public List<TkDataViewInterfaceDTO> filterByInterfaceType(@PathVariable("type") String type) 126 public List<TkDataViewInterfaceDTO> filterByInterfaceType(@PathVariable("type") String type)
135 throws ThingsboardException { 127 throws ThingsboardException {
136 return tkDataViewInterfaceService.filterByInterfaceType( 128 return tkDataViewInterfaceService.filterByInterfaceType(
@@ -139,7 +131,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -139,7 +131,6 @@ public class TkDataViewInterfaceController extends BaseController {
139 131
140 @GetMapping("/find_all_interface/{state}") 132 @GetMapping("/find_all_interface/{state}")
141 @ApiOperation("查询所有接口") 133 @ApiOperation("查询所有接口")
142 - @PreAuthorize("@check.checkPermissions({},{})")  
143 public List<TkDataViewInterfaceDTO> findAll(@PathVariable("state") String state) 134 public List<TkDataViewInterfaceDTO> findAll(@PathVariable("state") String state)
144 throws ThingsboardException { 135 throws ThingsboardException {
145 return tkDataViewInterfaceService.findAll(state, getCurrentUser().getCurrentTenantId()); 136 return tkDataViewInterfaceService.findAll(state, getCurrentUser().getCurrentTenantId());
@@ -147,7 +138,6 @@ public class TkDataViewInterfaceController extends BaseController { @@ -147,7 +138,6 @@ public class TkDataViewInterfaceController extends BaseController {
147 138
148 @GetMapping("/find/can_use_interfaces") 139 @GetMapping("/find/can_use_interfaces")
149 @ApiOperation("查询当前用户可使用的接口") 140 @ApiOperation("查询当前用户可使用的接口")
150 - @PreAuthorize("@check.checkPermissions({},{})")  
151 public List<TkDataViewInterfaceDTO> findCanUseInterfaces() throws ThingsboardException { 141 public List<TkDataViewInterfaceDTO> findCanUseInterfaces() throws ThingsboardException {
152 boolean isSysAdminOrPtAdmin = getCurrentUser().isPtAdmin() || getCurrentUser().isSystemAdmin(); 142 boolean isSysAdminOrPtAdmin = getCurrentUser().isPtAdmin() || getCurrentUser().isSystemAdmin();
153 return tkDataViewInterfaceService.findCanUseInterfaces( 143 return tkDataViewInterfaceService.findCanUseInterfaces(
  1 +package org.thingsboard.server.controller.yunteng;
  2 +
  3 +import io.swagger.annotations.Api;
  4 +import lombok.RequiredArgsConstructor;
  5 +import org.springframework.security.access.prepost.PreAuthorize;
  6 +import org.springframework.validation.annotation.Validated;
  7 +import org.springframework.web.bind.annotation.*;
  8 +import org.thingsboard.server.common.data.StringUtils;
  9 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  10 +import org.thingsboard.server.common.data.yunteng.common.AddGroup;
  11 +import org.thingsboard.server.common.data.yunteng.common.DeleteGroup;
  12 +import org.thingsboard.server.common.data.yunteng.common.UpdateGroup;
  13 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  14 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  15 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  16 +import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO;
  17 +import org.thingsboard.server.common.data.yunteng.dto.TkDeviceAccessInformationDTO;
  18 +import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
  19 +import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
  20 +import org.thingsboard.server.controller.BaseController;
  21 +import org.thingsboard.server.dao.yunteng.service.TkDeviceAccessInformationService;
  22 +
  23 +import java.util.HashMap;
  24 +
  25 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*;
  26 +
  27 +@RestController
  28 +@RequiredArgsConstructor
  29 +@RequestMapping("api/yt/device_profile/access_information")
  30 +@Api(tags = {"设备接入信息管理"})
  31 +public class TkDeviceAccessInformationController extends BaseController {
  32 +
  33 + private final TkDeviceAccessInformationService service;
  34 +
  35 + @GetMapping(params = {PAGE_SIZE, PAGE})
  36 + public TkPageData<TkDeviceAccessInformationDTO> page(
  37 + @RequestParam(PAGE_SIZE) int pageSize,
  38 + @RequestParam(PAGE) int page,
  39 + @RequestParam(value = "intranetIp", required = false) String intranetIp,
  40 + @RequestParam(value = "intranetPort", required = false) String intranetPort,
  41 + @RequestParam(value = "outerNetIp", required = false) String outerNetIp,
  42 + @RequestParam(value = "outerNetPort", required = false) String outerNetPort,
  43 + @RequestParam(value = "deviceAgreement", required = false) String deviceAgreement,
  44 + @RequestParam(value = ORDER_FILED, required = false) String orderBy) {
  45 +
  46 + HashMap<String, Object> queryMap = new HashMap<>();
  47 + queryMap.put(PAGE_SIZE, pageSize);
  48 + queryMap.put(PAGE, page);
  49 + queryMap.put(ORDER_FILED, orderBy);
  50 + queryMap.put("intranetIp", intranetIp);
  51 + queryMap.put("intranetPort", intranetPort);
  52 + queryMap.put("outerNetIp", outerNetIp);
  53 + queryMap.put("outerNetPort", outerNetPort);
  54 + queryMap.put("deviceAgreement", deviceAgreement);
  55 +
  56 + return service.page(queryMap);
  57 + }
  58 +
  59 + @PostMapping
  60 + @PreAuthorize(
  61 + "@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{'api:yt:device_profile:access_information:post'})")
  62 + public ResponseResult<TkDeviceAccessInformationDTO> saveAccessInfo(
  63 + @Validated({AddGroup.class}) @RequestBody TkDeviceAccessInformationDTO dto)
  64 + throws ThingsboardException {
  65 + if (!StringUtils.isEmpty(dto.getId())) {
  66 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  67 + }
  68 + return saveOrUpdate(dto);
  69 + }
  70 +
  71 + @PutMapping
  72 + @PreAuthorize(
  73 + "@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{'api:yt:device_profile:access_information:update'})")
  74 + public ResponseResult<TkDeviceAccessInformationDTO> updateAccessInfo(
  75 + @Validated({UpdateGroup.class}) @RequestBody TkDeviceAccessInformationDTO dto)
  76 + throws ThingsboardException {
  77 + if (StringUtils.isEmpty(dto.getId())) {
  78 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  79 + }
  80 + return saveOrUpdate(dto);
  81 + }
  82 +
  83 + @DeleteMapping
  84 + @PreAuthorize(
  85 + "@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{'api:yt:device_profile:access_information:delete'})")
  86 + public ResponseResult<String> deleteSysDict(
  87 + @Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO)
  88 + throws ThingsboardException {
  89 + deleteDTO.setTenantId(getCurrentUser().getCurrentTenantId());
  90 + return service.delete(deleteDTO)
  91 + ? ResponseResult.success(FastIotConstants.StateValue.DELETE_SUCCESS)
  92 + : ResponseResult.failed(FastIotConstants.StateValue.DELETE_FAILED);
  93 + }
  94 +
  95 + private ResponseResult<TkDeviceAccessInformationDTO> saveOrUpdate(
  96 + TkDeviceAccessInformationDTO dto) throws ThingsboardException {
  97 + dto.setTenantId(getCurrentUser().getCurrentTenantId());
  98 + TkDeviceAccessInformationDTO newDTO = service.saveOrUpdate(dto);
  99 + return ResponseResult.success(newDTO);
  100 + }
  101 +}
@@ -33,10 +33,7 @@ import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; @@ -33,10 +33,7 @@ import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
33 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; 33 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
34 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; 34 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
35 import org.thingsboard.server.common.data.yunteng.dto.*; 35 import org.thingsboard.server.common.data.yunteng.dto.*;
36 -import org.thingsboard.server.common.data.yunteng.enums.DataTypeEnum;  
37 -import org.thingsboard.server.common.data.yunteng.enums.DeviceState;  
38 -import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum;  
39 -import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; 36 +import org.thingsboard.server.common.data.yunteng.enums.*;
40 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult; 37 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
41 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; 38 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
42 import org.thingsboard.server.controller.BaseController; 39 import org.thingsboard.server.controller.BaseController;
@@ -225,7 +222,7 @@ public class TkDeviceController extends BaseController { @@ -225,7 +222,7 @@ public class TkDeviceController extends BaseController {
225 222
226 @GetMapping("{id}") 223 @GetMapping("{id}")
227 @ApiOperation("详情") 224 @ApiOperation("详情")
228 - @PreAuthorize("@check.checkPermissions({},{'api:yt:device:get'})") 225 + @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:device:get'})")
229 public ResponseEntity<DeviceDTO> getDevice(@PathVariable("id") String id) 226 public ResponseEntity<DeviceDTO> getDevice(@PathVariable("id") String id)
230 throws ThingsboardException { 227 throws ThingsboardException {
231 return ResponseEntity.of(tkdeviceService.getDevice(getCurrentUser().getCurrentTenantId(), id)); 228 return ResponseEntity.of(tkdeviceService.getDevice(getCurrentUser().getCurrentTenantId(), id));
@@ -239,7 +236,7 @@ public class TkDeviceController extends BaseController { @@ -239,7 +236,7 @@ public class TkDeviceController extends BaseController {
239 return ResponseEntity.of(tkdeviceService.getDevice(getCurrentUser().getCurrentTenantId(), id)); 236 return ResponseEntity.of(tkdeviceService.getDevice(getCurrentUser().getCurrentTenantId(), id));
240 } 237 }
241 238
242 - @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{})") 239 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN','CUSTOMER_USER')")
243 @PostMapping(params = {PAGE_SIZE, PAGE}) 240 @PostMapping(params = {PAGE_SIZE, PAGE})
244 @ApiOperation("查询") 241 @ApiOperation("查询")
245 public TkPageData<DeviceDTO> pageDevice( 242 public TkPageData<DeviceDTO> pageDevice(
@@ -265,7 +262,7 @@ public class TkDeviceController extends BaseController { @@ -265,7 +262,7 @@ public class TkDeviceController extends BaseController {
265 return tkdeviceService.page(getCurrentUser().getCurrentTenantId(), queryMap); 262 return tkdeviceService.page(getCurrentUser().getCurrentTenantId(), queryMap);
266 } 263 }
267 264
268 - @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{})") 265 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN','CUSTOMER_USER')")
269 @GetMapping( 266 @GetMapping(
270 path = {"/relation"}, 267 path = {"/relation"},
271 params = {PAGE_SIZE, PAGE}) 268 params = {PAGE_SIZE, PAGE})
@@ -383,14 +380,16 @@ public class TkDeviceController extends BaseController { @@ -383,14 +380,16 @@ public class TkDeviceController extends BaseController {
383 @ApiParam(value = "设备标签") @RequestParam(value = "deviceLabel", required = false) 380 @ApiParam(value = "设备标签") @RequestParam(value = "deviceLabel", required = false)
384 String deviceLabel, 381 String deviceLabel,
385 @ApiParam(value = "设备配置ID") @RequestParam(value = "deviceProfileId", required = false) 382 @ApiParam(value = "设备配置ID") @RequestParam(value = "deviceProfileId", required = false)
386 - String deviceProfileId) 383 + String deviceProfileId,
  384 + @ApiParam(value = "传输协议类型") @RequestParam(value = "transportType", required = false)
  385 + TransportTypeEnum transportType)
387 throws ThingsboardException { 386 throws ThingsboardException {
388 return tkdeviceService.findDevicesByDeviceTypeAndOrganizationId( 387 return tkdeviceService.findDevicesByDeviceTypeAndOrganizationId(
389 deviceType, 388 deviceType,
390 getCurrentUser().getCurrentTenantId(), 389 getCurrentUser().getCurrentTenantId(),
391 organizationId, 390 organizationId,
392 deviceLabel, 391 deviceLabel,
393 - deviceProfileId); 392 + deviceProfileId,transportType);
394 } 393 }
395 394
396 395
@@ -539,6 +538,12 @@ public class TkDeviceController extends BaseController { @@ -539,6 +538,12 @@ public class TkDeviceController extends BaseController {
539 tbDevice.setName(deviceDTO.getName()); 538 tbDevice.setName(deviceDTO.getName());
540 tbDevice.setCreatedTime(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()); 539 tbDevice.setCreatedTime(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());
541 tbDevice.setTenantId(tenantId); 540 tbDevice.setTenantId(tenantId);
  541 + tbDevice.setFirmwareId(
  542 + StringUtils.isEmpty(deviceDTO.getFirmwareId()) ? null :
  543 + new OtaPackageId(UUID.fromString(deviceDTO.getFirmwareId())));
  544 + tbDevice.setSoftwareId(
  545 + StringUtils.isEmpty(deviceDTO.getSoftwareId()) ? null :
  546 + new OtaPackageId(UUID.fromString(deviceDTO.getSoftwareId())));
542 return tbDevice; 547 return tbDevice;
543 } 548 }
544 549
@@ -14,6 +14,7 @@ import org.thingsboard.server.common.data.audit.ActionType; @@ -14,6 +14,7 @@ import org.thingsboard.server.common.data.audit.ActionType;
14 import org.thingsboard.server.common.data.edge.EdgeEventActionType; 14 import org.thingsboard.server.common.data.edge.EdgeEventActionType;
15 import org.thingsboard.server.common.data.exception.ThingsboardException; 15 import org.thingsboard.server.common.data.exception.ThingsboardException;
16 import org.thingsboard.server.common.data.id.DeviceProfileId; 16 import org.thingsboard.server.common.data.id.DeviceProfileId;
  17 +import org.thingsboard.server.common.data.id.OtaPackageId;
17 import org.thingsboard.server.common.data.id.RuleChainId; 18 import org.thingsboard.server.common.data.id.RuleChainId;
18 import org.thingsboard.server.common.data.id.TenantId; 19 import org.thingsboard.server.common.data.id.TenantId;
19 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 20 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@@ -119,16 +120,16 @@ public class TkDeviceProfileController extends BaseController { @@ -119,16 +120,16 @@ public class TkDeviceProfileController extends BaseController {
119 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{})") 120 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{})")
120 public ResponseEntity listDeviceProfile( 121 public ResponseEntity listDeviceProfile(
121 @ApiParam(value = "设备类型") @RequestParam(value = "deviceType", required = false) 122 @ApiParam(value = "设备类型") @RequestParam(value = "deviceType", required = false)
122 - DeviceTypeEnum deviceType) 123 + DeviceTypeEnum deviceType,DeviceTransportType transportType)
123 throws ThingsboardException { 124 throws ThingsboardException {
124 List<DeviceProfileDTO> results; 125 List<DeviceProfileDTO> results;
125 String tenantId = getCurrentUser().getCurrentTenantId(); 126 String tenantId = getCurrentUser().getCurrentTenantId();
126 if (getCurrentUser().isTenantAdmin()) { 127 if (getCurrentUser().isTenantAdmin()) {
127 - results = tkDeviceProfileService.findDeviceProfile(tenantId, null,deviceType); 128 + results = tkDeviceProfileService.findDeviceProfile(tenantId, null,deviceType,transportType);
128 } else { 129 } else {
129 results = 130 results =
130 tkDeviceProfileService.findCustomerDeviceProfiles( 131 tkDeviceProfileService.findCustomerDeviceProfiles(
131 - tenantId, getCurrentUser().getCustomerId(),deviceType); 132 + tenantId, getCurrentUser().getCustomerId(),deviceType,transportType);
132 } 133 }
133 134
134 return ResponseEntity.ok(results); 135 return ResponseEntity.ok(results);
@@ -257,6 +258,12 @@ public class TkDeviceProfileController extends BaseController { @@ -257,6 +258,12 @@ public class TkDeviceProfileController extends BaseController {
257 258
258 tbDeviceProfile.setProfileData( 259 tbDeviceProfile.setProfileData(
259 buildDeviceProfileData(transportType, deviceProfileDTO.getProfileData())); 260 buildDeviceProfileData(transportType, deviceProfileDTO.getProfileData()));
  261 + tbDeviceProfile.setFirmwareId(
  262 + StringUtils.isEmpty(deviceProfileDTO.getFirmwareId()) ? null :
  263 + new OtaPackageId(UUID.fromString(deviceProfileDTO.getFirmwareId())));
  264 + tbDeviceProfile.setSoftwareId(
  265 + StringUtils.isEmpty(deviceProfileDTO.getSoftwareId()) ? null :
  266 + new OtaPackageId(UUID.fromString(deviceProfileDTO.getSoftwareId())));
260 return tbDeviceProfile; 267 return tbDeviceProfile;
261 } 268 }
262 } 269 }
@@ -114,7 +114,7 @@ public class TkDeviceScriptController extends BaseController { @@ -114,7 +114,7 @@ public class TkDeviceScriptController extends BaseController {
114 114
115 @GetMapping(params = {PAGE_SIZE, PAGE}) 115 @GetMapping(params = {PAGE_SIZE, PAGE})
116 @ApiOperation("分页查询") 116 @ApiOperation("分页查询")
117 - @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{})") 117 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN','CUSTOMER_USER')")
118 public TkPageData<TkDeviceScriptDTO> pageDeviceScript( 118 public TkPageData<TkDeviceScriptDTO> pageDeviceScript(
119 @RequestParam(PAGE_SIZE) int pageSize, 119 @RequestParam(PAGE_SIZE) int pageSize,
120 @RequestParam(PAGE) int page, 120 @RequestParam(PAGE) int page,
@@ -209,7 +209,7 @@ public class TkDeviceScriptController extends BaseController { @@ -209,7 +209,7 @@ public class TkDeviceScriptController extends BaseController {
209 209
210 210
211 211
212 - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 212 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN','CUSTOMER_USER')")
213 @PostMapping("/modbus") 213 @PostMapping("/modbus")
214 @ApiOperation("生成Modbus指令") 214 @ApiOperation("生成Modbus指令")
215 public ResponseEntity<String> modbus(@RequestBody TkDeviceRpcDTO inputParams) 215 public ResponseEntity<String> modbus(@RequestBody TkDeviceRpcDTO inputParams)
  1 +package org.thingsboard.server.controller.yunteng;
  2 +
  3 +import io.swagger.annotations.Api;
  4 +import io.swagger.annotations.ApiOperation;
  5 +import io.swagger.annotations.ApiParam;
  6 +import lombok.RequiredArgsConstructor;
  7 +import org.springframework.security.access.prepost.PreAuthorize;
  8 +import org.springframework.web.bind.annotation.GetMapping;
  9 +import org.springframework.web.bind.annotation.RequestMapping;
  10 +import org.springframework.web.bind.annotation.RequestParam;
  11 +import org.springframework.web.bind.annotation.RestController;
  12 +import org.thingsboard.server.common.data.StringUtils;
  13 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  14 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  15 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  16 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
  17 +import org.thingsboard.server.common.data.yunteng.dto.sip.VideoChanelDTO;
  18 +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
  19 +import org.thingsboard.server.common.data.yunteng.enums.StatusEnum;
  20 +import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
  21 +import org.thingsboard.server.controller.BaseController;
  22 +import org.thingsboard.server.dao.yunteng.mapper.DeviceMapper;
  23 +import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  24 +import org.thingsboard.server.dao.yunteng.service.media.TkVideoChannelService;
  25 +
  26 +import java.util.HashMap;
  27 +import java.util.List;
  28 +
  29 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*;
  30 +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.ORDER_TYPE;
  31 +
  32 +@RestController
  33 +@RequestMapping("api/yt/video/channel")
  34 +@Api(tags = {"视频通道"})
  35 +@RequiredArgsConstructor
  36 +public class TkVideoChannelController extends BaseController {
  37 +
  38 + private final TkVideoChannelService tkVideoChannelService;
  39 + private final TkDeviceService tkDeviceService;
  40 + private final DeviceMapper deviceMapper;
  41 +
  42 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  43 + @GetMapping(params = {PAGE_SIZE, PAGE})
  44 + public TkPageData<VideoChanelDTO> pageData(
  45 + @RequestParam(PAGE_SIZE) int pageSize,
  46 + @RequestParam(PAGE) int page,
  47 + @RequestParam(value = ORDER_FILED, required = false) String orderBy,
  48 + @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType,
  49 + @ApiParam(value = "通道所属设备ID(tbDeviceId)")
  50 + @RequestParam(value = "tbDeviceId") String tbDeviceId,
  51 + @ApiParam(value = "视频通道名称")
  52 + @RequestParam(value = "name", required = false) String name,
  53 + @ApiParam(value = "设备状态")
  54 + @RequestParam(value = "status", required = false) StatusEnum status,
  55 + @ApiParam(value = "国标编号")
  56 + @RequestParam(value = "cameraCode", required = false) String cameraCode) throws ThingsboardException {
  57 + HashMap<String, Object> queryMap = new HashMap<>();
  58 + if(StringUtils.isNotEmpty(name)){
  59 + queryMap.put("name",name);
  60 + }
  61 + if(StringUtils.isNotEmpty(cameraCode)){
  62 + queryMap.put("cameraCode",cameraCode);
  63 + }
  64 + if(status!=null){
  65 + queryMap.put("status",status);
  66 + }
  67 + String tenantId = getCurrentUser().getCurrentTenantId();
  68 + if(StringUtils.isEmpty(tbDeviceId)){
  69 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  70 + }
  71 + if(getCurrentUser().isCustomerUser()){
  72 + DeviceDTO deviceDTO = deviceMapper.findDeviceByTbDeviceIdAndCustomerId(
  73 + tenantId,tbDeviceId,getCurrentUser().getCustomerId().toString());
  74 + if(null == deviceDTO){
  75 + throw new TkDataValidationException(ErrorMessage.NOT_BELONG_CURRENT_CUSTOMER.getMessage());
  76 + }
  77 + }
  78 + queryMap.put("tbDeviceId",tbDeviceId);
  79 + queryMap.put(PAGE_SIZE, pageSize);
  80 + queryMap.put(PAGE, page);
  81 + queryMap.put(ORDER_FILED, orderBy);
  82 + if (orderType != null) {
  83 + queryMap.put(ORDER_TYPE, orderType.name());
  84 + }
  85 + return tkVideoChannelService.page(tenantId, queryMap);
  86 + }
  87 +
  88 + @GetMapping("/list")
  89 + @ApiOperation("选项列表")
  90 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  91 + public List<VideoChanelDTO> listDeviceProfile(
  92 + @ApiParam(value = "设备Id") @RequestParam(value = "deviceId", required = false) String deviceId)
  93 + throws ThingsboardException {
  94 + String tenantId = getCurrentUser().getCurrentTenantId();
  95 + return tkVideoChannelService.getListByDeviceId(deviceId,tenantId);
  96 + }
  97 +}
@@ -11,6 +11,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -11,6 +11,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
11 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; 11 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup;
12 import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; 12 import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO;
13 import org.thingsboard.server.common.data.yunteng.dto.TkVideoDTO; 13 import org.thingsboard.server.common.data.yunteng.dto.TkVideoDTO;
  14 +import org.thingsboard.server.common.data.yunteng.dto.TkVideoGbtDTO;
14 import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; 15 import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;
15 import org.thingsboard.server.common.data.yunteng.utils.tools.ProtocolType; 16 import org.thingsboard.server.common.data.yunteng.utils.tools.ProtocolType;
16 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult; 17 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
@@ -30,7 +31,7 @@ import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant. @@ -30,7 +31,7 @@ import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.
30 @RequestMapping("api/yt/video") 31 @RequestMapping("api/yt/video")
31 @Api(tags = {"视频流"}) 32 @Api(tags = {"视频流"})
32 @RequiredArgsConstructor 33 @RequiredArgsConstructor
33 -@PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{})") 34 +@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
34 public class TkVideoController extends BaseController { 35 public class TkVideoController extends BaseController {
35 36
36 private final TkVideoService videoService; 37 private final TkVideoService videoService;
@@ -68,6 +69,17 @@ public class TkVideoController extends BaseController { @@ -68,6 +69,17 @@ public class TkVideoController extends BaseController {
68 return videoService.saveOrUpdate(dto); 69 return videoService.saveOrUpdate(dto);
69 } 70 }
70 71
  72 + @PostMapping("/add/gbt28181")
  73 + @ApiOperation("新增国标视频")
  74 + @PreAuthorize(
  75 + "@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:post','api:yt:video:update'})")
  76 + public TkVideoGbtDTO saveOrUpdateAlarmProfile(@Validated @RequestBody TkVideoGbtDTO dto)
  77 + throws ThingsboardException {
  78 + dto.setTenantId(getCurrentUser().getCurrentTenantId());
  79 + return videoService.saveGbt(dto);
  80 + }
  81 +
  82 +
71 @DeleteMapping 83 @DeleteMapping
72 @ApiOperation("删除") 84 @ApiOperation("删除")
73 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:delete'})") 85 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:delete'})")
@@ -138,8 +150,6 @@ public class TkVideoController extends BaseController { @@ -138,8 +150,6 @@ public class TkVideoController extends BaseController {
138 } 150 }
139 String url = 151 String url =
140 videoService.getCameraPreviewURL( 152 videoService.getCameraPreviewURL(
141 - getCurrentUser().isTenantAdmin(),  
142 - getCurrentUser().getCurrentUserId(),  
143 getCurrentUser().getCurrentTenantId(), 153 getCurrentUser().getCurrentTenantId(),
144 entityId, 154 entityId,
145 protocolType); 155 protocolType);
  1 +package org.thingsboard.server.controller.yunteng.media;
  2 +
  3 +import com.google.common.util.concurrent.MoreExecutors;
  4 +import io.swagger.annotations.Api;
  5 +import io.swagger.annotations.ApiOperation;
  6 +import io.swagger.annotations.ApiParam;
  7 +import io.swagger.v3.oas.annotations.Parameter;
  8 +import lombok.RequiredArgsConstructor;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.apache.curator.shaded.com.google.common.util.concurrent.FutureCallback;
  11 +import org.apache.curator.shaded.com.google.common.util.concurrent.Futures;
  12 +import org.apache.curator.shaded.com.google.common.util.concurrent.ListenableFuture;
  13 +import org.checkerframework.checker.nullness.qual.Nullable;
  14 +import org.jetbrains.annotations.NotNull;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.web.bind.annotation.*;
  17 +import org.springframework.web.context.request.async.DeferredResult;
  18 +import org.thingsboard.server.common.data.StringUtils;
  19 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  20 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  21 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  22 +import org.thingsboard.server.common.data.yunteng.dto.sip.StreamContentDTO;
  23 +import org.thingsboard.server.common.data.yunteng.enums.PTZCommandEnum;
  24 +import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
  25 +import org.thingsboard.server.controller.BaseController;
  26 +import org.thingsboard.server.service.yunteng.media.TkVideoControlService;
  27 +
  28 +@RestController
  29 +@RequestMapping("api/yt/video/control")
  30 +@Api(tags = {"视频控制管理"})
  31 +@RequiredArgsConstructor
  32 +@Slf4j
  33 +public class TkVideoControlController extends BaseController {
  34 +
  35 + private final TkVideoControlService tkVideoControlService;
  36 +
  37 + @GetMapping("/start/{deviceId}/{channelId}")
  38 + @ApiOperation(value = "视频点播/预览")
  39 +// @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:control:play'})")
  40 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN','CUSTOMER_USER')")
  41 + public DeferredResult<ResponseResult<StreamContentDTO>> startPlay(
  42 + @PathVariable("deviceId") String tbDeviceId, @PathVariable("channelId") String channelId)
  43 + throws ThingsboardException {
  44 + if (StringUtils.isEmpty(tbDeviceId) || StringUtils.isEmpty(channelId)) {
  45 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  46 + }
  47 + DeferredResult<ResponseResult<StreamContentDTO>> response = new DeferredResult<>();
  48 + ListenableFuture<StreamContentDTO> future =
  49 + tkVideoControlService.startPlay(getCurrentUser(),tbDeviceId, channelId);
  50 + Futures.addCallback(
  51 + future,
  52 + new FutureCallback<>() {
  53 + @Override
  54 + public void onSuccess(@Nullable StreamContentDTO streamContentDTO) {
  55 + response.setResult(ResponseResult.success(streamContentDTO));
  56 + }
  57 +
  58 + @Override
  59 + public void onFailure(@NotNull Throwable throwable) {
  60 + response.setResult(ResponseResult.failed(throwable.getMessage()));
  61 + }
  62 + },
  63 + MoreExecutors.directExecutor());
  64 + return response;
  65 + }
  66 + @GetMapping("/sync/{deviceId}")
  67 + @ApiOperation(value = "摄像头通道同步")
  68 + @PreAuthorize(
  69 + "@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:control:channel'})")
  70 + public DeferredResult<ResponseResult<String>> freshChannel(
  71 + @PathVariable("deviceId") String tbDeviceId)
  72 + throws ThingsboardException {
  73 + if (StringUtils.isEmpty(tbDeviceId) ) {
  74 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  75 + }
  76 + DeferredResult<ResponseResult<String>> response = new DeferredResult<>();
  77 + ListenableFuture<String> future =
  78 + tkVideoControlService.freshChannel(getCurrentUser(),tbDeviceId);
  79 + Futures.addCallback(
  80 + future,
  81 + new FutureCallback<>() {
  82 + @Override
  83 + public void onSuccess(@Nullable String result) {
  84 + response.setResult(ResponseResult.success(result));
  85 + }
  86 +
  87 + @Override
  88 + public void onFailure(@NotNull Throwable throwable) {
  89 + response.setResult(ResponseResult.failed(throwable.getMessage()));
  90 + }
  91 + },
  92 + MoreExecutors.directExecutor());
  93 + return response;
  94 + }
  95 +
  96 + @ApiOperation(value = "停止点播")
  97 + @GetMapping("/stop/{deviceId}/{channelId}")
  98 + @PreAuthorize(
  99 + "@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:control:stop'})")
  100 + public ResponseResult playStop(
  101 + @ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String tbDeviceId,
  102 + @ApiParam(value = "通道ID", required = true) @PathVariable("channelId") String channelId)
  103 + throws ThingsboardException {
  104 + if (StringUtils.isEmpty(tbDeviceId) || StringUtils.isEmpty(channelId)) {
  105 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  106 + }
  107 + return ResponseResult.success(
  108 + tkVideoControlService.stopPlay(getCurrentUser(),tbDeviceId, channelId));
  109 + }
  110 +
  111 + @ApiOperation(value = "云台控制")
  112 + @Parameter(name = "tbDeviceId", description = "TB设备ID", required = true)
  113 + @Parameter(name = "channelId", description = "通道国标编号", required = true)
  114 + @Parameter(
  115 + name = "command",
  116 + description =
  117 + "控制指令,允许值: LEFT, RIGHT, UP, DOWN, UP_LEFT, UPRIGHT, DOWN_LEFT, DOWN_RIGHT, ZOOM_IN, ZOOM_OUT, STOP",
  118 + required = true)
  119 + @Parameter(name = "horizonSpeed", description = "水平速度", required = true)
  120 + @Parameter(name = "verticalSpeed", description = "垂直速度", required = true)
  121 + @Parameter(name = "zoomSpeed", description = "缩放速度", required = true)
  122 + @GetMapping("/control/{tbDeviceId}/{channelId}")
  123 + @PreAuthorize(
  124 + "@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN','CUSTOMER_USER'},{'api:yt:video:control:control'})")
  125 + public void ptzControl(
  126 + @PathVariable String tbDeviceId,
  127 + @PathVariable String channelId,
  128 + PTZCommandEnum command,
  129 + int horizonSpeed,
  130 + int verticalSpeed,
  131 + int zoomSpeed)
  132 + throws ThingsboardException {
  133 + ResponseResult.success(
  134 + tkVideoControlService.control(getCurrentUser(),tbDeviceId, channelId,command,horizonSpeed,verticalSpeed,zoomSpeed));
  135 + }
  136 +}
  1 +package org.thingsboard.server.controller.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import io.swagger.annotations.Api;
  5 +import io.swagger.annotations.ApiOperation;
  6 +import lombok.RequiredArgsConstructor;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.web.bind.annotation.*;
  9 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  10 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  11 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
  12 +import org.thingsboard.server.common.data.yunteng.dto.sip.VideoChanelDTO;
  13 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.param.*;
  14 +import org.thingsboard.server.controller.BaseController;
  15 +import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  16 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerService;
  17 +import org.thingsboard.server.dao.yunteng.service.media.TkVideoChannelService;
  18 +import org.thingsboard.server.service.yunteng.media.TkVideoControlService;
  19 +
  20 +/** ZLMediaServer的hook事件监听 */
  21 +@RestController
  22 +@RequestMapping("api/index/hook")
  23 +@Api(tags = {"ZLMediaServer的hook事件监听"})
  24 +@RequiredArgsConstructor
  25 +@Slf4j
  26 +public class ZLMediaKitHookController extends BaseController {
  27 + private final TkMediaServerService tkMediaServerService;
  28 + private final TkVideoChannelService tkVideoChannelService;
  29 + private final TkVideoControlService controlService;
  30 +
  31 + @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
  32 + @ApiOperation(value = "保持流媒体服务器的心跳")
  33 + public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
  34 + // log.error("ZLMediaKitHook保持流媒体服务器的心跳【on_server_keepalive】,API参数【{}】",
  35 + // JacksonUtil.toString(param));
  36 + return tkMediaServerService.zlmOnServerKeepalive(param);
  37 + }
  38 +
  39 + @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
  40 + @ApiOperation(value = "播放器鉴权事件")
  41 + public HookResult onPlay(@RequestBody BaseParam param) {
  42 + return tkMediaServerService.zlmOnPlay(param);
  43 + }
  44 +
  45 + @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
  46 + @ApiOperation(value = "rtsp/rtmp/rtp推流鉴权事件")
  47 + public HookResultForOnPublish onPublish(@RequestBody BaseParam param) {
  48 + return tkMediaServerService.onPublish(param);
  49 + }
  50 +
  51 + @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
  52 + @ApiOperation(value = "rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感")
  53 + public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
  54 + return tkMediaServerService.zlmOnStreamChanged(param);
  55 + }
  56 +
  57 + @ResponseBody
  58 + @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
  59 + @ApiOperation(value = "流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流")
  60 + public JsonNode onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) throws ThingsboardException {
  61 + JsonNode result = tkMediaServerService.zlmOnStreamNoneReader(param);
  62 + if(result.has(FastIotConstants.ZLMediaBody.CHANNEL_ID) &&result.has(FastIotConstants.ZLMediaBody.CAMERA_CODE)){
  63 + String channelId = result.get(FastIotConstants.ZLMediaBody.CHANNEL_ID).asText();
  64 + String cameraCode = result.get(FastIotConstants.ZLMediaBody.CAMERA_CODE).asText();
  65 + VideoChanelDTO chanelDTO = tkVideoChannelService.findVideoChannelById(cameraCode,channelId, null);
  66 + controlService.byeCmdInSsrcTransaction(chanelDTO.getTenantId(),
  67 + false,
  68 + chanelDTO.getDeviceId(),
  69 + cameraCode,channelId,result.get(FastIotConstants.ZLMediaBody.SSRCINFO_STREAM).asText(),
  70 + fromDeviceRpcResponse->{
  71 +
  72 + });
  73 + tkVideoChannelService.updateVideoChannelStreamId(null, cameraCode, channelId);//storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  74 + }
  75 + return result;
  76 + }
  77 +}
@@ -15,6 +15,9 @@ @@ -15,6 +15,9 @@
15 */ 15 */
16 package org.thingsboard.server.service.transport; 16 package org.thingsboard.server.service.transport;
17 17
  18 +import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;
  19 +import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
  20 +
18 import com.fasterxml.jackson.core.JsonProcessingException; 21 import com.fasterxml.jackson.core.JsonProcessingException;
19 import com.fasterxml.jackson.databind.JsonNode; 22 import com.fasterxml.jackson.databind.JsonNode;
20 import com.fasterxml.jackson.databind.ObjectMapper; 23 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -23,8 +26,18 @@ import com.google.common.util.concurrent.Futures; @@ -23,8 +26,18 @@ import com.google.common.util.concurrent.Futures;
23 import com.google.common.util.concurrent.ListenableFuture; 26 import com.google.common.util.concurrent.ListenableFuture;
24 import com.google.common.util.concurrent.MoreExecutors; 27 import com.google.common.util.concurrent.MoreExecutors;
25 import com.google.protobuf.ByteString; 28 import com.google.protobuf.ByteString;
  29 +import java.util.List;
  30 +import java.util.Optional;
  31 +import java.util.Set;
  32 +import java.util.UUID;
  33 +import java.util.concurrent.ConcurrentHashMap;
  34 +import java.util.concurrent.ConcurrentMap;
  35 +import java.util.concurrent.locks.Lock;
  36 +import java.util.concurrent.locks.ReentrantLock;
  37 +import java.util.stream.Collectors;
26 import lombok.RequiredArgsConstructor; 38 import lombok.RequiredArgsConstructor;
27 import lombok.extern.slf4j.Slf4j; 39 import lombok.extern.slf4j.Slf4j;
  40 +import org.apache.zookeeper.Op;
28 import org.springframework.stereotype.Service; 41 import org.springframework.stereotype.Service;
29 import org.thingsboard.common.util.JacksonUtil; 42 import org.thingsboard.common.util.JacksonUtil;
30 import org.thingsboard.server.cache.ota.OtaPackageDataCache; 43 import org.thingsboard.server.cache.ota.OtaPackageDataCache;
@@ -60,9 +73,14 @@ import org.thingsboard.server.common.data.page.PageLink; @@ -60,9 +73,14 @@ import org.thingsboard.server.common.data.page.PageLink;
60 import org.thingsboard.server.common.data.relation.EntityRelation; 73 import org.thingsboard.server.common.data.relation.EntityRelation;
61 import org.thingsboard.server.common.data.security.DeviceCredentials; 74 import org.thingsboard.server.common.data.security.DeviceCredentials;
62 import org.thingsboard.server.common.data.security.DeviceCredentialsType; 75 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  76 +import org.thingsboard.server.common.data.yunteng.common.media.VideoStreamSessionManager;
  77 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
63 import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; 78 import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
64 import org.thingsboard.server.common.data.yunteng.dto.TkDeviceScriptDTO; 79 import org.thingsboard.server.common.data.yunteng.dto.TkDeviceScriptDTO;
65 -import org.thingsboard.server.common.data.yunteng.enums.TkScriptFunctionType; 80 +import org.thingsboard.server.common.data.yunteng.dto.sip.SipDeviceDTO;
  81 +import org.thingsboard.server.common.data.yunteng.dto.sip.SsrcTransactionDTO;
  82 +import org.thingsboard.server.common.data.yunteng.dto.sip.VideoChanelDTO;
  83 +import org.thingsboard.server.common.data.yunteng.enums.VideoCmdEnum;
66 import org.thingsboard.server.common.msg.EncryptionUtil; 84 import org.thingsboard.server.common.msg.EncryptionUtil;
67 import org.thingsboard.server.common.msg.TbMsg; 85 import org.thingsboard.server.common.msg.TbMsg;
68 import org.thingsboard.server.common.msg.TbMsgDataType; 86 import org.thingsboard.server.common.msg.TbMsgDataType;
@@ -77,8 +95,11 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; @@ -77,8 +95,11 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse;
77 import org.thingsboard.server.dao.ota.OtaPackageService; 95 import org.thingsboard.server.dao.ota.OtaPackageService;
78 import org.thingsboard.server.dao.relation.RelationService; 96 import org.thingsboard.server.dao.relation.RelationService;
79 import org.thingsboard.server.dao.tenant.TbTenantProfileCache; 97 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
  98 +import org.thingsboard.server.dao.yunteng.entities.TkVideoChannelEntity;
80 import org.thingsboard.server.dao.yunteng.service.TkDeviceScriptService; 99 import org.thingsboard.server.dao.yunteng.service.TkDeviceScriptService;
81 import org.thingsboard.server.dao.yunteng.service.TkDeviceService; 100 import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  101 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerNodeService;
  102 +import org.thingsboard.server.dao.yunteng.service.media.TkVideoChannelService;
82 import org.thingsboard.server.gen.transport.TransportProtos; 103 import org.thingsboard.server.gen.transport.TransportProtos;
83 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; 104 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
84 import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; 105 import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg;
@@ -105,19 +126,6 @@ import org.thingsboard.server.service.install.DefaultSystemDataLoaderService; @@ -105,19 +126,6 @@ import org.thingsboard.server.service.install.DefaultSystemDataLoaderService;
105 import org.thingsboard.server.service.profile.TbDeviceProfileCache; 126 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
106 import org.thingsboard.server.service.resource.TbResourceService; 127 import org.thingsboard.server.service.resource.TbResourceService;
107 128
108 -import java.util.Arrays;  
109 -import java.util.List;  
110 -import java.util.Optional;  
111 -import java.util.UUID;  
112 -import java.util.concurrent.ConcurrentHashMap;  
113 -import java.util.concurrent.ConcurrentMap;  
114 -import java.util.concurrent.locks.Lock;  
115 -import java.util.concurrent.locks.ReentrantLock;  
116 -import java.util.stream.Collectors;  
117 -  
118 -import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;  
119 -import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;  
120 -  
121 /** 129 /**
122 * Created by ashvayka on 05.10.18. 130 * Created by ashvayka on 05.10.18.
123 */ 131 */
@@ -135,6 +143,10 @@ public class DefaultTransportApiService implements TransportApiService { @@ -135,6 +143,10 @@ public class DefaultTransportApiService implements TransportApiService {
135 private final DeviceService deviceService; 143 private final DeviceService deviceService;
136 private final TkDeviceService ytDeviceService; 144 private final TkDeviceService ytDeviceService;
137 private final TkDeviceScriptService scriptService; 145 private final TkDeviceScriptService scriptService;
  146 + private final TkVideoChannelService channelService;
  147 + private final TkMediaServerNodeService mediaServerNodeService;
  148 + private final VideoStreamSessionManager videoStreamSessionManager;
  149 +
138 private final RelationService relationService; 150 private final RelationService relationService;
139 private final DeviceCredentialsService deviceCredentialsService; 151 private final DeviceCredentialsService deviceCredentialsService;
140 private final DbCallbackExecutorService dbCallbackExecutorService; 152 private final DbCallbackExecutorService dbCallbackExecutorService;
@@ -192,6 +204,9 @@ public class DefaultTransportApiService implements TransportApiService { @@ -192,6 +204,9 @@ public class DefaultTransportApiService implements TransportApiService {
192 else if (transportApiRequestMsg.hasScript()) { 204 else if (transportApiRequestMsg.hasScript()) {
193 result = handle(transportApiRequestMsg.getScript()); 205 result = handle(transportApiRequestMsg.getScript());
194 } 206 }
  207 + else if (transportApiRequestMsg.hasGbt28181RequestMsg()) {
  208 + result = handleGbt(transportApiRequestMsg.getGbt28181RequestMsg());
  209 + }
195 210
196 211
197 return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture), 212 return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture),
@@ -685,20 +700,76 @@ public class DefaultTransportApiService implements TransportApiService { @@ -685,20 +700,76 @@ public class DefaultTransportApiService implements TransportApiService {
685 private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.ScriptProto requestMsg) { 700 private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.ScriptProto requestMsg) {
686 List<TkDeviceScriptDTO> allScriptes = scriptService.getScriptes(); 701 List<TkDeviceScriptDTO> allScriptes = scriptService.getScriptes();
687 TransportApiResponseMsg.Builder responseBuilder = TransportApiResponseMsg.newBuilder(); 702 TransportApiResponseMsg.Builder responseBuilder = TransportApiResponseMsg.newBuilder();
688 - allScriptes.forEach(  
689 - item -> {  
690 - UUID tenantId = UUID.fromString(item.getTenantId());  
691 - UUID id = UUID.fromString(item.getId());  
692 - responseBuilder.addScriptsResponseMsg(  
693 - TransportProtos.ScriptProto.newBuilder()  
694 - .setConvertJs(item.getConvertJs())  
695 - .setTenantIdLSB(tenantId.getLeastSignificantBits())  
696 - .setTenantIdMSB(tenantId.getMostSignificantBits())  
697 - .setScriptIdLSB(id.getLeastSignificantBits())  
698 - .setScriptIdMSB(id.getMostSignificantBits())  
699 - .setFunctionType(item.getScriptType().name())  
700 - .setStatus(item.getStatus()));  
701 - }); 703 + allScriptes.forEach(
  704 + item -> {
  705 + UUID tenantId = UUID.fromString(item.getTenantId());
  706 + UUID id = UUID.fromString(item.getId());
  707 + responseBuilder.addScriptsResponseMsg(
  708 + TransportProtos.ScriptProto.newBuilder()
  709 + .setConvertJs(item.getConvertJs())
  710 + .setTenantIdLSB(tenantId.getLeastSignificantBits())
  711 + .setTenantIdMSB(tenantId.getMostSignificantBits())
  712 + .setScriptIdLSB(id.getLeastSignificantBits())
  713 + .setScriptIdMSB(id.getMostSignificantBits())
  714 + .setFunctionType(item.getScriptType().name())
  715 + .setStatus(item.getStatus()));
  716 + });
702 return Futures.immediateFuture(responseBuilder.build()); 717 return Futures.immediateFuture(responseBuilder.build());
703 } 718 }
  719 + private ListenableFuture<TransportApiResponseMsg> handleGbt(TransportProtos.Gbt28181RequestMsg requestMsg) {
  720 + TransportProtos.Gbt28181ResponseMsg.Builder responseMsgBuilder =TransportProtos.Gbt28181ResponseMsg.newBuilder();
  721 + String tenantId = new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()).toString();
  722 + String tbDeviceId = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB()).toString();
  723 + String type = requestMsg.getContextType();
  724 + switch (VideoCmdEnum.valueOf(type)){
  725 + case DeviceInfo:
  726 + Optional<SipDeviceDTO> camera = dataDecodingEncodingService.decode(requestMsg.getContext().toByteArray());
  727 + camera.ifPresent(sip ->{
  728 + ytDeviceService.updateDeviceInfo(tenantId,tbDeviceId, FastIotConstants.DeviceAdditional.SIP,JacksonUtil.valueToTree(sip));
  729 + });
  730 +
  731 + break;
  732 + case Catalog:
  733 + Optional<List<VideoChanelDTO>> allChannel = dataDecodingEncodingService.decode(requestMsg.getContext().toByteArray());
  734 + allChannel.ifPresent(d->{
  735 + channelService.clearDeviceChannel(d.get(0).getCameraCode());
  736 + List<TkVideoChannelEntity> chanel= d.stream().map(item -> item.getEntity(TkVideoChannelEntity.class)).collect(Collectors.toList());
  737 + channelService.insertBatch(chanel,chanel.size());
  738 + });
  739 + break;
  740 + default:
  741 + release(tenantId,tbDeviceId);
  742 +
  743 + }
  744 +// byte[] channaelMsgBytes = dataDecodingEncodingService.encode(channelDTO);
  745 +// responseMsgBuilder.setContext(ByteString.copyFrom(channaelMsgBytes));
  746 + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setGbt28181ResponseMsg(responseMsgBuilder.build()).build());
  747 + }
  748 + private void release(String tenantId,String tbDeviceId){
  749 + DeviceDTO device = ytDeviceService.findDeviceInfoByTbDeviceId(tenantId,tbDeviceId);
  750 + Optional.ofNullable(device).ifPresent(dev->{
  751 + JsonNode sip = dev.getDeviceInfo().get(FastIotConstants.DeviceAdditional.SIP);
  752 + if(!sip.isEmpty() && sip.has(FastIotConstants.ZLMediaBody.CAMERA_CODE)){
  753 + String cameraCode = sip.get(FastIotConstants.ZLMediaBody.CAMERA_CODE).asText();
  754 + Optional<Set<String>> ssrcKeys =
  755 + videoStreamSessionManager.getSsrcTransactionCamaraKey(cameraCode);
  756 + ssrcKeys.ifPresent(all ->{
  757 + all.forEach(fullKey->{
  758 + videoStreamSessionManager.getSsrcTransaction(fullKey).ifPresent(ssrcTransaction -> {
  759 + mediaServerNodeService.releaseSsrc(
  760 + ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
  761 + mediaServerNodeService.closeRTPServer(
  762 + ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
  763 + videoStreamSessionManager.remove(
  764 + cameraCode, ssrcTransaction.getChannelId(),
  765 + ssrcTransaction.getStream());
  766 + });
  767 +
  768 + });
  769 + });
  770 + }
  771 + });
  772 +
  773 +
  774 + }
704 } 775 }
  1 +package org.thingsboard.server.service.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import org.apache.curator.shaded.com.google.common.util.concurrent.ListenableFuture;
  5 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  6 +import org.thingsboard.server.common.data.yunteng.dto.sip.*;
  7 +import org.thingsboard.server.common.data.yunteng.enums.PTZCommandEnum;
  8 +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
  9 +import org.thingsboard.server.service.security.model.SecurityUser;
  10 +
  11 +import java.util.function.Consumer;
  12 +
  13 +public interface TkVideoControlService {
  14 +
  15 + /**
  16 + * 视频点播
  17 + *
  18 + * @param deviceId 设备ID
  19 + * @param channelId 通道ID
  20 + * @return 视频流播放地址内容
  21 + */
  22 + ListenableFuture<StreamContentDTO> startPlay(
  23 + SecurityUser currentUser, String deviceId, String channelId);
  24 +
  25 + /**
  26 + * 摄像头通道同步
  27 + *
  28 + * @param deviceId 设备ID
  29 + * @return 视频流播放地址内容
  30 + */
  31 + ListenableFuture<String> freshChannel(SecurityUser currentUser, String deviceId);
  32 +
  33 + /**
  34 + * 当点播时的推送处理程序
  35 + *
  36 + * @param mediaServerItem 流媒体信息
  37 + * @param response 响应
  38 + * @param deviceId 点播设备ID
  39 + * @param channelId 点播设备通道ID
  40 + */
  41 + StreamInfoDTO onPublishHandlerForPlay(
  42 + MediaServerDTO mediaServerItem, JsonNode response, String deviceId, String channelId);
  43 +
  44 + /**
  45 + * 通过设备ID和通道ID暂停播放
  46 + *
  47 + * @param tbDeviceId TB设备的ID
  48 + * @param channelId 通道ID
  49 + * @return 暂停播放结果 true成功 false失败
  50 + */
  51 + boolean stopPlay(SecurityUser currentUser, String tbDeviceId, String channelId)
  52 + throws ThingsboardException;
  53 +
  54 + /**
  55 + * 摄像头控制
  56 + *
  57 + * @param currentUser 登录用户
  58 + * @param tbDeviceId 设备ID
  59 + * @param channelId 设备通道
  60 + * @param command 控制指令
  61 + * @param horizonSpeed 水平速度
  62 + * @param verticalSpeed 垂直速度
  63 + * @param zoomSpeed 缩放速度
  64 + * @return
  65 + */
  66 + boolean control(
  67 + SecurityUser currentUser,
  68 + String tbDeviceId,
  69 + String channelId,
  70 + PTZCommandEnum command,
  71 + int horizonSpeed,
  72 + int verticalSpeed,
  73 + int zoomSpeed)
  74 + throws ThingsboardException;
  75 +
  76 + StreamInfoDTO play(
  77 + SecurityUser currentUser,
  78 + String tbDeviceId,
  79 + MediaServerDTO mediaServerItem,
  80 + SsrcInfoDTO ssrcInfo,
  81 + SipDeviceDTO device,
  82 + String channelId);
  83 +
  84 + public void byeCmdInSsrcTransaction(
  85 + String tenantId,
  86 + boolean oneWay,
  87 + String tbDeviceId,
  88 + String cameraCode,
  89 + String channelId,
  90 + String streamId,
  91 + Consumer<FromDeviceRpcResponse> responseConsumer)
  92 + throws ThingsboardException;
  93 +}
  1 +package org.thingsboard.server.service.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import com.fasterxml.jackson.databind.node.ObjectNode;
  5 +import java.util.Optional;
  6 +import java.util.UUID;
  7 +import java.util.Vector;
  8 +import java.util.concurrent.CountDownLatch;
  9 +import java.util.concurrent.TimeUnit;
  10 +import java.util.concurrent.atomic.AtomicReference;
  11 +import java.util.function.Consumer;
  12 +import javax.sdp.*;
  13 +import lombok.RequiredArgsConstructor;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import org.apache.curator.shaded.com.google.common.util.concurrent.Futures;
  16 +import org.apache.curator.shaded.com.google.common.util.concurrent.ListenableFuture;
  17 +import org.springframework.beans.factory.annotation.Value;
  18 +import org.springframework.stereotype.Service;
  19 +import org.thingsboard.server.common.data.StringUtils;
  20 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
  21 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  22 +import org.thingsboard.server.common.data.id.DeviceId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
  25 +import org.thingsboard.server.common.data.yunteng.common.media.VideoStreamSessionManager;
  26 +import org.thingsboard.server.common.data.yunteng.common.media.ZlmHttpHookSubscribe;
  27 +import org.thingsboard.server.common.data.yunteng.config.media.UserSetting;
  28 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  29 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  30 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  31 +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO;
  32 +import org.thingsboard.server.common.data.yunteng.dto.sip.*;
  33 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.param.HookSubscribeForStreamChange;
  34 +import org.thingsboard.server.common.data.yunteng.enums.*;
  35 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  36 +import org.thingsboard.server.common.data.yunteng.utils.ZLMediaKitRestFulUtils;
  37 +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
  38 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  39 +import org.thingsboard.server.dao.util.yunteng.ZLMediaKitTaskUtils;
  40 +import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
  41 +import org.thingsboard.server.dao.yunteng.service.media.*;
  42 +import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
  43 +import org.thingsboard.server.service.security.model.SecurityUser;
  44 +
  45 +@Service
  46 +@RequiredArgsConstructor
  47 +@Slf4j
  48 +public class TkVideoControlServiceImpl implements TkVideoControlService {
  49 +
  50 + private final TkMediaServerService tkMediaServerService;
  51 + private final TkMediaServerNodeService tkMediaServerNodeService;
  52 + private final TkDeviceService tkDeviceService;
  53 + private final TkVideoChannelService tkVideoChannelService;
  54 + private final TkCacheStorageService tkCacheStorageService;
  55 + private final ZLMediaKitTaskUtils zlMediaKitTaskUtils;
  56 + private final UserSetting userSetting;
  57 + private final ZlmHttpHookSubscribe subscribe;
  58 + private final VideoStreamSessionManager videoStreamSessionManager;
  59 + private final ZLMediaKitRestFulUtils zlMediaKitRestFulUtils;
  60 + private final TbCoreDeviceRpcService deviceRpcService;
  61 +
  62 + @Override
  63 + public ListenableFuture<StreamContentDTO> startPlay(
  64 + SecurityUser currentUser, String deviceId, String channelId) {
  65 + String tenantId = currentUser.getCurrentTenantId();
  66 + // 判断设备、通道是否存在
  67 + DeviceDTO deviceDTO = tkDeviceService.checkDeviceByTenantIdAndId(tenantId, deviceId, true);
  68 + SipDeviceDTO sipDeviceDTO =
  69 + JacksonUtil.convertValue(
  70 + deviceDTO.getDeviceInfo().get(FastIotConstants.DeviceAdditional.SIP),
  71 + SipDeviceDTO.class);
  72 + // 获取设备的附加信息
  73 + if (null == sipDeviceDTO) {
  74 + throw new TkDataValidationException(ErrorMessage.FOUND_VIDEO_DEVICE_FAILED.getMessage());
  75 + }
  76 + VideoChanelDTO videoChanelDTO =
  77 + tkVideoChannelService.findVideoChannelById(
  78 + sipDeviceDTO.getCameraCode(), channelId, tenantId);
  79 + if (null == videoChanelDTO) {
  80 + throw new TkDataValidationException(ErrorMessage.VIDEO_CHANNEL_NOT_FOUND.getMessage());
  81 + }
  82 + // 找到可以使用的流媒体
  83 + MediaServerDTO mediaServer =
  84 + tkMediaServerService.getMediaServerForPlay(tenantId, sipDeviceDTO.getMediaServerId());
  85 + if (null == mediaServer) {
  86 + throw new TkDataValidationException(
  87 + ErrorMessage.NOT_FOUND_MEDIA_SERVER_FOR_PLAY.getMessage());
  88 + }
  89 + // 根据设备ID和通道ID,找到对应的流信息
  90 + Optional<StreamInfoDTO> result =
  91 + tkCacheStorageService.queryPlayStreamByChannel(sipDeviceDTO.getCameraCode(), channelId);
  92 + if (result.isPresent()) {
  93 + StreamInfoDTO streamInfoDTO = result.get();
  94 + String streamId = streamInfoDTO.getStream();
  95 + if (StringUtils.isNotEmpty(streamId)) {
  96 + String mediaServerId = streamInfoDTO.getMediaServerId();
  97 + Optional<MediaServerDTO> mediaServerDTO = tkMediaServerNodeService.getOne(mediaServerId);
  98 + if (mediaServerDTO.isPresent()) {
  99 + if (checkRTPServerExist(mediaServerDTO.get(), streamInfoDTO, tenantId)) {
  100 + log.debug(String.format("缓存的【流媒体播放信息】内容【%s】", streamInfoDTO));
  101 + return Futures.immediateFuture(new StreamContentDTO(streamInfoDTO));
  102 + }
  103 + }
  104 + }
  105 + }
  106 +
  107 + StreamInfoDTO streamInfoDTO =
  108 + deviceFirstPlay(
  109 + currentUser, deviceDTO.getTbDeviceId(), mediaServer, sipDeviceDTO, videoChanelDTO);
  110 + if (Optional.ofNullable(streamInfoDTO).isPresent()) {
  111 + return Futures.immediateFuture(new StreamContentDTO(streamInfoDTO));
  112 + } else {
  113 + return Futures.immediateFuture(new StreamContentDTO(null));
  114 + }
  115 + }
  116 +
  117 + @Override
  118 + public ListenableFuture<String> freshChannel(SecurityUser currentUser, String deviceId) {
  119 + String tenantId = currentUser.getCurrentTenantId();
  120 + // 判断设备、通道是否存在
  121 + Optional<DeviceDTO> device = Optional.ofNullable(tkDeviceService.checkDeviceByTenantIdAndId(tenantId, deviceId, true));
  122 + if(device.isEmpty()){
  123 + return Futures.immediateFuture(ErrorMessage.DEVICE_NOT_EXTIED.getMessage());
  124 + }
  125 + DeviceDTO deviceDTO = device.get();
  126 + if (!deviceDTO.getDeviceState().equals(DeviceState.ONLINE)) {
  127 + return Futures.immediateFuture(ErrorMessage.DEVICE_NOT_ONLINE.getMessage());
  128 + }
  129 + SipDeviceDTO sipDeviceDTO =
  130 + JacksonUtil.convertValue(
  131 + deviceDTO.getDeviceInfo().get(FastIotConstants.DeviceAdditional.SIP),
  132 + SipDeviceDTO.class);
  133 + // 获取设备的附加信息
  134 + if (null == sipDeviceDTO) {
  135 + throw new TkDataValidationException(ErrorMessage.FOUND_VIDEO_DEVICE_FAILED.getMessage());
  136 + }
  137 + CountDownLatch timeoutLatch = new CountDownLatch(1);
  138 + AtomicReference<String> result = new AtomicReference<>();
  139 + // 进行命令发送
  140 +
  141 +
  142 + ObjectNode paramJson = JacksonUtil.newObjectNode();
  143 + paramJson.put(FastIotConstants.ZLMediaBody.MSG_TYPE, VideoXmlEnum.Query.name());
  144 + int sn = (int) ((Math.random() * 9 + 1) * 100000);
  145 + paramJson.put(FastIotConstants.ZLMediaBody.MSG_CONTEXT, sn);
  146 +
  147 + try {
  148 + cameraCommonCmd(
  149 + currentUser.getCurrentTenantId(),
  150 + paramJson,
  151 + sipDeviceDTO.getCameraCode(),
  152 + VideoMethodEnum.MESSAGE,
  153 + false,
  154 + deviceId,
  155 + fromDeviceRpcResponse -> {
  156 + log.warn(
  157 + "【流媒体SIP】收到【视频点播】结果=异常【{}】+数据【{}】",
  158 + fromDeviceRpcResponse.getError(),
  159 + fromDeviceRpcResponse.getResponse());
  160 + fromDeviceRpcResponse
  161 + .getResponse()
  162 + .ifPresent(
  163 + jsonStr -> {
  164 + JsonNode responseJson = JacksonUtil.toJsonNode(jsonStr);
  165 + if (fromDeviceRpcResponse.getError().isEmpty()) {
  166 + result.set(jsonStr);
  167 + } else {
  168 + result.set(fromDeviceRpcResponse.getError().get().name());
  169 + }
  170 + });
  171 + timeoutLatch.countDown();
  172 + });
  173 + } catch (ThingsboardException e) {
  174 + Futures.immediateFailedFuture(e);
  175 + }
  176 + return Futures.immediateFuture(result.get());
  177 +
  178 + }
  179 +
  180 + @Override
  181 + public StreamInfoDTO onPublishHandlerForPlay(
  182 + MediaServerDTO mediaServerItem, JsonNode response, String deviceCode, String channelId) {
  183 + StreamInfoDTO streamInfo = onPublishHandler(mediaServerItem, response, deviceCode, channelId);
  184 + VideoChanelDTO videoChanel =
  185 + tkVideoChannelService.findVideoChannelById(
  186 + deviceCode, channelId, mediaServerItem.getTenantId());
  187 + if (videoChanel != null) {
  188 + videoChanel.setStreamId(streamInfo.getStream());
  189 + tkVideoChannelService.updateVideoChannelStreamId(
  190 + streamInfo.getStream(), deviceCode, channelId);
  191 + }
  192 + tkCacheStorageService.cacheStreamInfoByStartPlay(streamInfo);
  193 + return streamInfo;
  194 + }
  195 +
  196 + @Override
  197 + public boolean stopPlay(SecurityUser currentUser, String tbDeviceId, String channelId)
  198 + throws ThingsboardException {
  199 + String tenantId = currentUser.getCurrentTenantId();
  200 + DeviceDTO deviceDTO = tkDeviceService.findDeviceInfoByTbDeviceId(tenantId, tbDeviceId);
  201 + if (null == deviceDTO
  202 + || !deviceDTO.getDeviceInfo().has(FastIotConstants.DeviceAdditional.SIP)) {
  203 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  204 + }
  205 + String cameraCode =
  206 + deviceDTO
  207 + .getDeviceInfo()
  208 + .get(FastIotConstants.DeviceAdditional.SIP)
  209 + .get(FastIotConstants.ZLMediaBody.CAMERA_CODE)
  210 + .asText();
  211 +
  212 + Optional<StreamInfoDTO> streamInfoDTO =
  213 + tkCacheStorageService.queryPlayStreamByChannel(cameraCode, channelId);
  214 + if (streamInfoDTO.isEmpty()) {
  215 + throw new TkDataValidationException(ErrorMessage.STREAM_INFO_NOT_FOUND_FOR_PLAY.getMessage());
  216 + }
  217 + byeCmdInSsrcTransaction(
  218 + currentUser.getCurrentTenantId(),
  219 + false,
  220 + tbDeviceId,
  221 + cameraCode,
  222 + channelId,
  223 + streamInfoDTO.get().getStream(),
  224 + fromDeviceRpcResponse -> {});
  225 +
  226 + tkCacheStorageService.deleteCacheStreamInfoByStopPlay(
  227 + streamInfoDTO.get()); // redisCatchStorage.stopPlay(streamInfo);
  228 + return tkVideoChannelService.updateVideoChannelStreamId(
  229 + null,
  230 + cameraCode,
  231 + channelId); // storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  232 + }
  233 +
  234 + @Override
  235 + public StreamInfoDTO play(
  236 + SecurityUser currentUser,
  237 + String tbDeviceId,
  238 + MediaServerDTO mediaServerDTO,
  239 + SsrcInfoDTO ssrcInfoDTO,
  240 + SipDeviceDTO sipDeviceDTO,
  241 + String channelId) {
  242 + HookSubscribeForStreamChange hookSubscribe =
  243 + new HookSubscribeForStreamChange(
  244 + "rtp", ssrcInfoDTO.getStream(), true, "rtsp", mediaServerDTO.getMediaServerId());
  245 + CountDownLatch timeoutLatch = new CountDownLatch(1);
  246 + // 定时任务超时KEY
  247 + String timeOutTaskKey = UUID.randomUUID().toString();
  248 + zlMediaKitTaskUtils.startCronTaskDelay(
  249 + timeOutTaskKey,
  250 + () -> {
  251 + log.debug(
  252 + "[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}",
  253 + sipDeviceDTO.getCameraCode(),
  254 + channelId,
  255 + ssrcInfoDTO.getPort(),
  256 + ssrcInfoDTO.getSsrc());
  257 + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  258 + try {
  259 + // cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  260 + // TODO 回复Bye命令
  261 + } catch (Exception e) {
  262 + log.error("[点播超时], 发送BYE失败 {}", e.getMessage());
  263 + } finally {
  264 + // timeoutCallback.run(1, "收流超时");
  265 + // 释放SSRC
  266 + tkMediaServerNodeService.releaseSsrc(
  267 + mediaServerDTO.getMediaServerId(), ssrcInfoDTO.getSsrc());
  268 + // streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  269 + // 关闭RTP服务
  270 + tkMediaServerNodeService.closeRTPServer(
  271 + Optional.of(mediaServerDTO), ssrcInfoDTO.getStream());
  272 + // 取消订阅消息监听
  273 + subscribe.removeSubscribe(hookSubscribe);
  274 + }
  275 + },
  276 + userSetting.getPlayTimeout());
  277 + // 获取端口信息失败,则不发送点播指令
  278 + if (ssrcInfoDTO.getPort() <= FastIotConstants.MagicNumber.ZERO) {
  279 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  280 + // 释放SSRC
  281 + tkMediaServerNodeService.releaseSsrc(
  282 + mediaServerDTO.getMediaServerId(), ssrcInfoDTO.getSsrc());
  283 + videoStreamSessionManager.remove(
  284 + sipDeviceDTO.getCameraCode(), channelId, ssrcInfoDTO.getStream());
  285 + throw new TkDataValidationException(
  286 + String.format(ErrorMessage.GET_PLAY_PORT_FAILED.getMessage(), mediaServerDTO.getIp()));
  287 + }
  288 + // 进行命令发送
  289 + AtomicReference<StreamInfoDTO> result = new AtomicReference<>();
  290 + try {
  291 +
  292 + log.debug(
  293 + String.format(
  294 + "视频流【%s】的流媒体服务器信息=流媒体服务ID【%s】+流媒体服务端口【%s】+SDP(会话)IP【%s】",
  295 + ssrcInfoDTO.getStream(),
  296 + mediaServerDTO.getMediaServerId(),
  297 + ssrcInfoDTO.getPort(),
  298 + mediaServerDTO.getSdpIp()));
  299 + ObjectNode paramJson = JacksonUtil.newObjectNode();
  300 + paramJson.set(FastIotConstants.ZLMediaBody.MEDIA, JacksonUtil.valueToTree(mediaServerDTO));
  301 + //
  302 + // paramJson.put(FastIotConstants.ZLMediaBody.MEDIA_ID,mediaServerDTO.getMediaServerId());
  303 + // paramJson.put(FastIotConstants.ZLMediaBody.MEDIA_SDP_IP,mediaServerDTO.getSdpIp());
  304 + paramJson.put(FastIotConstants.ZLMediaBody.SSRCINFO_STREAM, ssrcInfoDTO.getStream());
  305 + paramJson.put(FastIotConstants.ZLMediaBody.SSRCINFO_PORT, ssrcInfoDTO.getPort());
  306 + paramJson.put(FastIotConstants.ZLMediaBody.SSRCINFO_SSRC, ssrcInfoDTO.getSsrc());
  307 + paramJson.put(FastIotConstants.ZLMediaBody.CHANNEL_ID, channelId);
  308 + // TODO: 2023/12/28 收到流改变事件后,视频点播的业务逻辑。
  309 + subscribe.addSubscribe(
  310 + hookSubscribe,
  311 + (MediaServerDTO mediaServerItemInUse, JsonNode response) -> {
  312 + log.debug(
  313 + "处理订阅消息:主题【{}】流媒体信息【{}】收到的内容【{}】",
  314 + hookSubscribe.getHookType().name(),
  315 + mediaServerItemInUse,
  316 + response);
  317 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  318 +
  319 + subscribe.removeSubscribe(hookSubscribe);
  320 + result.set(
  321 + steamImage(
  322 + ssrcInfoDTO.getStream(),
  323 + sipDeviceDTO.getCameraCode(),
  324 + channelId,
  325 + mediaServerItemInUse,
  326 + response));
  327 + timeoutLatch.countDown();
  328 + });
  329 + cameraCommonCmd(
  330 + currentUser.getCurrentTenantId(),
  331 + paramJson,
  332 + sipDeviceDTO.getCameraCode(),
  333 + VideoMethodEnum.INVITE,
  334 + false,
  335 + tbDeviceId,
  336 + fromDeviceRpcResponse -> {
  337 + log.warn(
  338 + "【流媒体SIP】收到【视频点播】结果=异常【{}】+数据【{}】",
  339 + fromDeviceRpcResponse.getError(),
  340 + fromDeviceRpcResponse.getResponse());
  341 + fromDeviceRpcResponse
  342 + .getResponse()
  343 + .ifPresent(
  344 + jsonStr -> {
  345 + JsonNode responseJson = JacksonUtil.toJsonNode(jsonStr);
  346 + if (fromDeviceRpcResponse.getError().isEmpty()) {
  347 + playSuccess(
  348 + currentUser,
  349 + tbDeviceId,
  350 + mediaServerDTO,
  351 + responseJson,
  352 + ssrcInfoDTO,
  353 + sipDeviceDTO,
  354 + channelId,
  355 + timeOutTaskKey);
  356 + } else {
  357 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  358 + tkMediaServerNodeService.closeRTPServer(
  359 + mediaServerDTO, ssrcInfoDTO.getStream(), null);
  360 + // 释放ssrc
  361 + tkMediaServerNodeService.releaseSsrc(
  362 + mediaServerDTO.getMediaServerId(), ssrcInfoDTO.getSsrc());
  363 + videoStreamSessionManager.remove(
  364 + sipDeviceDTO.getCameraCode(), channelId, ssrcInfoDTO.getStream());
  365 + // errorEvent.response(event);
  366 + }
  367 + });
  368 + });
  369 + timeoutLatch.await(userSetting.getPlayTimeout(), TimeUnit.MILLISECONDS);
  370 + } catch (Exception e) {
  371 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  372 + tkMediaServerNodeService.closeRTPServer(
  373 + Optional.of(mediaServerDTO), ssrcInfoDTO.getStream());
  374 + // 释放ssrc
  375 + tkMediaServerNodeService.releaseSsrc(mediaServerDTO.getId(), ssrcInfoDTO.getSsrc());
  376 +
  377 + videoStreamSessionManager.remove(
  378 + sipDeviceDTO.getCameraCode(), channelId, ssrcInfoDTO.getStream());
  379 +
  380 + // SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
  381 + // eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent;
  382 + // eventResult.statusCode = -1;
  383 + // eventResult.msg = "命令发送失败";
  384 + // errorEvent.response(eventResult);
  385 + }
  386 + log.debug(String.format("最新的【流媒体播放信息】内容【%s】", result.get()));
  387 + return result.get();
  388 + }
  389 +
  390 + /**
  391 + * 调用流媒体接口获取视频流封面图,并触发后续业务
  392 + *
  393 + * @param ssrcStream
  394 + * @param cameraCode
  395 + * @param channelId
  396 + * @param mediaServerItemInUse
  397 + * @param response
  398 + */
  399 + private StreamInfoDTO steamImage(
  400 + String ssrcStream,
  401 + String cameraCode,
  402 + String channelId,
  403 + MediaServerDTO mediaServerItemInUse,
  404 + JsonNode response) {
  405 + // hook响应
  406 + StreamInfoDTO streamInfoDTO =
  407 + onPublishHandlerForPlay(mediaServerItemInUse, response, cameraCode, channelId);
  408 + String streamUrl;
  409 + if (mediaServerItemInUse.getRtspPort() != 0) {
  410 + streamUrl =
  411 + String.format(
  412 + "rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInUse.getRtspPort(), "rtp", ssrcStream);
  413 + } else {
  414 + streamUrl =
  415 + String.format(
  416 + "http://127.0.0.1:%s/%s/%s.live.mp4",
  417 + mediaServerItemInUse.getHttpPort(), "rtp", ssrcStream);
  418 + }
  419 + String path = "snap";
  420 + String fileName = cameraCode + "_" + channelId + ".jpg";
  421 + // 请求截图
  422 + log.debug("[请求截图]: " + fileName);
  423 + zlMediaKitRestFulUtils.getSnap(mediaServerItemInUse, streamUrl, 15, 1, path, fileName);
  424 + return streamInfoDTO;
  425 + }
  426 +
  427 + /**
  428 + * 构建视频点播的流媒体信息
  429 + *
  430 + * @param mediaServerItem
  431 + * @param response
  432 + * @param deviceCode
  433 + * @param channelId
  434 + * @return
  435 + */
  436 + private StreamInfoDTO onPublishHandler(
  437 + MediaServerDTO mediaServerItem, JsonNode response, String deviceCode, String channelId) {
  438 + String streamId = response.get("stream").asText();
  439 + JsonNode tracks = response.get("tracks");
  440 + StreamInfoDTO streamInfo =
  441 + tkMediaServerService.getStreamInfoByAppAndStream(
  442 + mediaServerItem, "rtp", streamId, tracks, null);
  443 + streamInfo.setCameraCode(deviceCode);
  444 + streamInfo.setChannelId(channelId);
  445 + return streamInfo;
  446 + }
  447 +
  448 + private StreamInfoDTO deviceFirstPlay(
  449 + SecurityUser currentUser,
  450 + String tbDeviceId,
  451 + MediaServerDTO mediaServerDTO,
  452 + SipDeviceDTO sipDeviceDTO,
  453 + VideoChanelDTO videoChanelDTO) {
  454 + // 进行点播时,需要的参数包含:schema、vhost、app、stream
  455 + String streamId = null;
  456 + // 是否开启了多端口模式
  457 + if (mediaServerDTO.isRtpEnable()) {
  458 + streamId =
  459 + String.format("%s_%s", sipDeviceDTO.getCameraCode(), videoChanelDTO.getChannelId());
  460 + }
  461 + // 开启收流服务
  462 + SsrcInfoDTO ssrcInfoDTO =
  463 + tkMediaServerNodeService.openRTPServer(
  464 + mediaServerDTO,
  465 + streamId,
  466 + null,
  467 + sipDeviceDTO.isSsrcCheck(),
  468 + false,
  469 + 0,
  470 + false,
  471 + sipDeviceDTO.getStreamModeForParam());
  472 + if (null == ssrcInfoDTO) {
  473 + throw new TkDataValidationException(ErrorMessage.RECEIVE_STREAM_FAILED.getMessage());
  474 + }
  475 + // 开始进行点播
  476 + return play(
  477 + currentUser,
  478 + tbDeviceId,
  479 + mediaServerDTO,
  480 + ssrcInfoDTO,
  481 + sipDeviceDTO,
  482 + videoChanelDTO.getChannelId());
  483 + }
  484 +
  485 + private boolean checkRTPServerExist(
  486 + MediaServerDTO mediaServerDTO, StreamInfoDTO streamInfo, String tenantId) {
  487 + String streamId = streamInfo.getStream();
  488 + JsonNode rtpInfo = zlMediaKitRestFulUtils.getRtpInfo(mediaServerDTO, streamId);
  489 + if (rtpInfo.get("code").asInt() == 0 && rtpInfo.get("exist").asBoolean()) {
  490 + int localPort = rtpInfo.get("local_port").asInt();
  491 + if (localPort == 0) {
  492 + throw new TkDataValidationException("点播已经在进行中,请稍候重试");
  493 + }
  494 + return true;
  495 + } else {
  496 + String deviceId = streamInfo.getCameraCode();
  497 + String channelId = streamInfo.getChannelId();
  498 + tkCacheStorageService.deleteCacheStreamInfoByStopPlay(streamInfo);
  499 + tkVideoChannelService.updateVideoChannelStreamId(null, deviceId, channelId);
  500 + return false;
  501 + }
  502 + }
  503 +
  504 + @Value("${server.rest.server_side_rpc.min_timeout:5000}")
  505 + protected long minTimeout;
  506 +
  507 + @Value("${server.rest.server_side_rpc.default_timeout:10000}")
  508 + protected long defaultTimeout;
  509 +
  510 + /**
  511 + * 公用的命令下发接口
  512 + *
  513 + * @param tenantId
  514 + * @param paramJson
  515 + * @param oneWay
  516 + * @param deviceId TB的设备ID
  517 + * @param responseConsumer
  518 + * @throws ThingsboardException
  519 + */
  520 + public void cameraCommonCmd(
  521 + String tenantId,
  522 + ObjectNode paramJson,
  523 + String cameraCode,
  524 + VideoMethodEnum method,
  525 + boolean oneWay,
  526 + String deviceId,
  527 + Consumer<FromDeviceRpcResponse> responseConsumer)
  528 + throws ThingsboardException {
  529 + try {
  530 +
  531 + paramJson.put(FastIotConstants.ZLMediaBody.CAMERA_CODE, cameraCode);
  532 + paramJson.put(FastIotConstants.ZLMediaBody.METHOD_TYPE, method.name());
  533 + ToDeviceRpcRequestBody body =
  534 + new ToDeviceRpcRequestBody("cameraMethod", JacksonUtil.toString(paramJson));
  535 + long timeout = defaultTimeout;
  536 + long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
  537 + UUID rpcRequestUUID = UUID.randomUUID();
  538 + boolean persisted = false;
  539 + ToDeviceRpcRequest rpcRequest =
  540 + new ToDeviceRpcRequest(
  541 + rpcRequestUUID,
  542 + new TenantId(UUID.fromString(tenantId)),
  543 + new DeviceId(UUID.fromString(deviceId)),
  544 + oneWay,
  545 + expTime,
  546 + body,
  547 + persisted,
  548 + null,
  549 + null);
  550 + deviceRpcService.processRestApiRpcRequest(rpcRequest, responseConsumer, null);
  551 + } catch (IllegalArgumentException ioe) {
  552 + throw new ThingsboardException(
  553 + "Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  554 + }
  555 + }
  556 +
  557 + /**
  558 + * 点播关闭命令下发
  559 + *
  560 + * @param oneWay
  561 + * @param tbDeviceId TB的设备ID
  562 + * @param responseConsumer
  563 + * @throws ThingsboardException
  564 + */
  565 + @Override
  566 + public void byeCmdInSsrcTransaction(
  567 + String tenantId,
  568 + boolean oneWay,
  569 + String tbDeviceId,
  570 + String cameraCode,
  571 + String channelId,
  572 + String streamId,
  573 + Consumer<FromDeviceRpcResponse> responseConsumer)
  574 + throws ThingsboardException {
  575 + Optional<SsrcTransactionDTO> transactionDTO =
  576 + videoStreamSessionManager.getSsrcTransaction(cameraCode, channelId, streamId);
  577 + if (transactionDTO.isEmpty()) {
  578 + throw new TkDataValidationException(ErrorMessage.STREAM_INFO_NOT_FOUND_FOR_PLAY.getMessage());
  579 + }
  580 + SsrcTransactionDTO ssrc = transactionDTO.get();
  581 + cameraByeCmd(
  582 + tenantId,
  583 + ssrc.getSipTransactionInfo(),
  584 + oneWay,
  585 + true,
  586 + tbDeviceId,
  587 + cameraCode,
  588 + channelId,
  589 + streamId,
  590 + ssrc.getSsrc(),
  591 + ssrc.getMediaServerId(),
  592 + responseConsumer);
  593 + }
  594 +
  595 + public void byeCmdInSendRtp(
  596 + SecurityUser currentUser,
  597 + boolean oneWay,
  598 + String tbDeviceId,
  599 + String cameraCode,
  600 + String channelId,
  601 + String streamId,
  602 + Consumer<FromDeviceRpcResponse> responseConsumer)
  603 + throws ThingsboardException {
  604 +
  605 + // SendRtpItemDTO sendRtpItem =null;
  606 + // SipMessageHeaderDTO sipTransactionInfo =
  607 + // SipMessageHeaderDTO.builder()
  608 + // .toTag(sendRtpItem.getToTag())
  609 + // .fromTag(sendRtpItem.getFromTag())
  610 + // .callId(sendRtpItem.getCallId())
  611 + // .build();
  612 + // MediaServerDTO mediaServer =
  613 + // tkMediaServerService.getMediaServerByMediaServerId(sendRtpItem.getMediaServerId());
  614 + // cameraByeCmd(
  615 + // currentUser,ssrc.getSipTransactionInfo(),oneWay,mediaServer != null,tbDeviceId,
  616 + //
  617 + // cameraCode,sendRtpItem.getChannelId(),streamId,ssrc.getSsrc(),ssrc.getMediaServerId(),
  618 + // responseConsumer);
  619 + }
  620 +
  621 + public void cameraByeCmd(
  622 + String currentUser,
  623 + SipMessageHeaderDTO messageHeaderDTO,
  624 + boolean oneWay,
  625 + boolean mediaOnline,
  626 + String tbDeviceId,
  627 + String cameraCode,
  628 + String channelId,
  629 + String streamId,
  630 + String ssrc,
  631 + String mediaServerId,
  632 + Consumer<FromDeviceRpcResponse> responseConsumer)
  633 + throws ThingsboardException {
  634 + if (mediaOnline) {
  635 + tkMediaServerNodeService.releaseSsrc(mediaServerId, ssrc);
  636 + tkMediaServerNodeService.closeRTPServer(mediaServerId, streamId);
  637 + }
  638 + videoStreamSessionManager.remove(cameraCode, channelId, streamId);
  639 + ObjectNode paramJson = JacksonUtil.newObjectNode();
  640 + paramJson.put(FastIotConstants.ZLMediaBody.CHANNEL_ID, channelId);
  641 + paramJson.set(
  642 + FastIotConstants.ZLMediaBody.MSG_HEADER, JacksonUtil.valueToTree(messageHeaderDTO));
  643 + cameraCommonCmd(
  644 + currentUser,
  645 + paramJson,
  646 + cameraCode,
  647 + VideoMethodEnum.BYE,
  648 + oneWay,
  649 + tbDeviceId,
  650 + responseConsumer);
  651 + }
  652 +
  653 + /**
  654 + * 点播成功后,流媒体的业务逻辑
  655 + *
  656 + * @param currentUser
  657 + * @param tbDeviceId
  658 + * @param mediaServerDTO
  659 + * @param responseJson
  660 + * @param ssrcInfo
  661 + * @param sipDeviceDTO
  662 + * @param channelId
  663 + * @param timeOutTaskKey
  664 + */
  665 + private void playSuccess(
  666 + SecurityUser currentUser,
  667 + String tbDeviceId,
  668 + MediaServerDTO mediaServerDTO,
  669 + JsonNode responseJson,
  670 + SsrcInfoDTO ssrcInfo,
  671 + SipDeviceDTO sipDeviceDTO,
  672 + String channelId,
  673 + String timeOutTaskKey) {
  674 + String cameraCode = sipDeviceDTO.getCameraCode();
  675 + String callId = responseJson.get(FastIotConstants.ZLMediaBody.CALL_ID).asText();
  676 + JsonNode msgHeader = responseJson.get(FastIotConstants.ZLMediaBody.MSG_HEADER);
  677 + SipMessageHeaderDTO sipMessage = JacksonUtil.convertValue(msgHeader, SipMessageHeaderDTO.class);
  678 + String sessionType = responseJson.get(FastIotConstants.ZLMediaBody.SESSION_TYPE).asText();
  679 + String streamId = ssrcInfo.getStream();
  680 + videoStreamSessionManager.put(
  681 + cameraCode,
  682 + channelId,
  683 + callId,
  684 + streamId,
  685 + ssrcInfo.getSsrc(),
  686 + sipMessage,
  687 + mediaServerDTO.getMediaServerId(),
  688 + SessionTypeEnum.valueOf(sessionType));
  689 + // 获取ssrc
  690 + String contentString = responseJson.get(FastIotConstants.ZLMediaBody.MSG_CONTEXT).asText();
  691 + int ssrcIndex = contentString.indexOf("y=");
  692 + // 检查是否有y字段
  693 + if (ssrcIndex >= 0) {
  694 + // ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  695 + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim();
  696 + // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  697 + if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  698 + if (sipDeviceDTO.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
  699 + String substring = contentString.substring(0, contentString.indexOf("y="));
  700 + try {
  701 + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
  702 + int port = -1;
  703 + Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  704 + for (Object description : mediaDescriptions) {
  705 + MediaDescription mediaDescription = (MediaDescription) description;
  706 + Media media = mediaDescription.getMedia();
  707 +
  708 + Vector mediaFormats = media.getMediaFormats(false);
  709 + if (mediaFormats.contains("96")) {
  710 + port = media.getMediaPort();
  711 + break;
  712 + }
  713 + }
  714 + log.debug(
  715 + "[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
  716 + sipDeviceDTO.getCameraCode(),
  717 + channelId,
  718 + sdp.getConnection().getAddress(),
  719 + port,
  720 + sipDeviceDTO.getStreamMode(),
  721 + ssrcInfo.getSsrc(),
  722 + sipDeviceDTO.isSsrcCheck());
  723 + JsonNode jsonNode =
  724 + zlMediaKitRestFulUtils.connectRtpServer(
  725 + mediaServerDTO, sdp.getConnection().getAddress(), port, streamId);
  726 + log.debug("[点播-TCP主动连接对方] 结果: {}", jsonNode);
  727 + } catch (SdpException e) {
  728 + log.error(
  729 + "[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败",
  730 + sipDeviceDTO.getCameraCode(),
  731 + channelId,
  732 + e);
  733 + }
  734 + }
  735 + return;
  736 + }
  737 + log.debug("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  738 + if (!mediaServerDTO.isRtpEnable() || sipDeviceDTO.isSsrcCheck()) {
  739 + log.debug("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  740 +
  741 + // 释放不被使用的ssrc
  742 + tkMediaServerNodeService.releaseSsrc(mediaServerDTO.getMediaServerId(), ssrcInfo.getSsrc());
  743 + // 单端口模式streamId也有变化,需要重新设置监听
  744 + if (!mediaServerDTO.isRtpEnable()) {
  745 + // 添加订阅
  746 + HookSubscribeForStreamChange hookSubscribe =
  747 + new HookSubscribeForStreamChange(
  748 + "rtp", streamId, true, "rtsp", mediaServerDTO.getMediaServerId());
  749 + subscribe.removeSubscribe(hookSubscribe);
  750 + hookSubscribe
  751 + .getContent()
  752 + .put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  753 + subscribe.addSubscribe(
  754 + hookSubscribe,
  755 + (MediaServerDTO mediaServerItemInUse, JsonNode response) -> {
  756 + log.debug(
  757 + "处理订阅消息:主题【{}】流媒体信息【{}】收到的内容【{}】",
  758 + hookSubscribe.getHookType().name(),
  759 + mediaServerItemInUse,
  760 + response);
  761 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  762 + // hook响应
  763 + onPublishHandlerForPlay(mediaServerItemInUse, response, cameraCode, channelId);
  764 + // hookEvent.response(mediaServerItemInUse, response);
  765 + });
  766 + }
  767 +
  768 + // 关闭rtp server
  769 + tkMediaServerNodeService.closeRTPServer(
  770 + mediaServerDTO,
  771 + streamId,
  772 + result -> {
  773 + if (result) {
  774 + // 重新开启ssrc server
  775 + tkMediaServerNodeService.openRTPServer(
  776 + mediaServerDTO,
  777 + streamId,
  778 + ssrcInResponse,
  779 + sipDeviceDTO.isSsrcCheck(),
  780 + false,
  781 + ssrcInfo.getPort(),
  782 + true,
  783 + sipDeviceDTO.getStreamModeForParam());
  784 + } else {
  785 + try {
  786 + log.warn("[停止点播] {}/{}", cameraCode, channelId);
  787 + byeCmdInSsrcTransaction(
  788 + currentUser.getCurrentTenantId(),
  789 + false,
  790 + tbDeviceId,
  791 + cameraCode,
  792 + channelId,
  793 + streamId,
  794 + fromDeviceRpcResponse -> {});
  795 + } catch (Exception e) {
  796 + log.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
  797 + }
  798 + zlMediaKitTaskUtils.stop(timeOutTaskKey);
  799 + videoStreamSessionManager.remove(cameraCode, channelId, streamId);
  800 + // event.msg = "下级自定义了ssrc,重新设置收流信息失败";
  801 + // event.statusCode = 500;
  802 + // errorEvent.response(event);
  803 + }
  804 + });
  805 + }
  806 + }
  807 + }
  808 +
  809 + @Override
  810 + public boolean control(
  811 + SecurityUser currentUser,
  812 + String tbDeviceId,
  813 + String channelId,
  814 + PTZCommandEnum command,
  815 + int horizonSpeed,
  816 + int verticalSpeed,
  817 + int zoomSpeed)
  818 + throws ThingsboardException {
  819 +
  820 + if (PTZCommandEnum.STOP.equals(command)) {
  821 + horizonSpeed = 0;
  822 + verticalSpeed = 0;
  823 + zoomSpeed = 0;
  824 + }
  825 + DeviceDTO deviceDTO =
  826 + tkDeviceService.checkDeviceByTenantIdAndId(
  827 + currentUser.getCurrentTenantId(), tbDeviceId, true);
  828 + if (null == deviceDTO) {
  829 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  830 + }
  831 + SipDeviceDTO sipDeviceDTO =
  832 + JacksonUtil.convertValue(
  833 + deviceDTO.getDeviceInfo().get(FastIotConstants.DeviceAdditional.SIP),
  834 + SipDeviceDTO.class);
  835 + if (null == sipDeviceDTO) {
  836 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  837 + }
  838 + PTZCmdDTO ptzCmdDTO = new PTZCmdDTO();
  839 + ptzCmdDTO.setCommand(command);
  840 + ptzCmdDTO.setHorizonSpeed(horizonSpeed);
  841 + ptzCmdDTO.setVerticalSpeed(verticalSpeed);
  842 + ptzCmdDTO.setZoomSpeed(zoomSpeed);
  843 + ObjectNode requestJson = JacksonUtil.newObjectNode();
  844 + requestJson.put(FastIotConstants.ZLMediaBody.CHANNEL_ID, channelId);
  845 + requestJson.put(FastIotConstants.ZLMediaBody.MSG_TYPE, VideoXmlEnum.Control.name());
  846 + requestJson.set(
  847 + FastIotConstants.ZLMediaBody.MSG_CONTEXT, JacksonUtil.valueToTree(ptzCmdDTO));
  848 + cameraCommonCmd(
  849 + currentUser.getCurrentTenantId(),
  850 + requestJson,
  851 + sipDeviceDTO.getCameraCode(),
  852 + VideoMethodEnum.MESSAGE,
  853 + false,
  854 + tbDeviceId,
  855 + fromDeviceRpcResponse -> {});
  856 + return false;
  857 + }
  858 +}
  1 +package org.thingsboard.server.service.yunteng.media;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import lombok.Getter;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.beans.factory.annotation.Value;
  7 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  8 +import org.springframework.stereotype.Component;
  9 +import org.thingsboard.server.common.data.id.EntityId;
  10 +import org.thingsboard.server.common.data.yunteng.config.media.ZLMediaKitServerConfig;
  11 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  12 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
  13 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  14 +import org.thingsboard.server.common.data.yunteng.utils.ZLMediaKitRestFulUtils;
  15 +import org.thingsboard.server.dao.util.yunteng.ZLMediaKitTaskUtils;
  16 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerNodeService;
  17 +import org.thingsboard.server.dao.yunteng.service.media.TkMediaServerService;
  18 +import org.thingsboard.server.common.data.yunteng.config.media.MediaConfig;
  19 +import org.thingsboard.server.queue.util.TbCoreComponent;
  20 +
  21 +import javax.annotation.PostConstruct;
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +import java.util.Optional;
  25 +
  26 +@Component("ZLMediaKitState")
  27 +@TbCoreComponent
  28 +@ConditionalOnExpression(
  29 + "'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.gbt28181.enabled}'=='true')")
  30 +@Slf4j
  31 +public class ZLMediaKitStateRunner {
  32 + private final ZLMediaKitRestFulUtils zlMediaKitRestFulUtils;
  33 + private final TkMediaServerNodeService tkMediaServerNodeService;
  34 + private final TkMediaServerService tkMediaServerService;
  35 + private final MediaConfig mediaConfig;
  36 + private final ZLMediaKitTaskUtils zlMediaKitTaskUtils;
  37 + private final Map<String, Boolean> startGetMedia;
  38 +
  39 + @Value("${media.defaultStateCheckIntervalInSec}")
  40 + @Getter
  41 + private int defaultStateCheckIntervalInSec;
  42 +
  43 + public ZLMediaKitStateRunner(
  44 + ZLMediaKitRestFulUtils zlMediaKitRestFulUtils,
  45 + TkMediaServerNodeService tkMediaServerNodeService,
  46 + TkMediaServerService tkMediaServerService,
  47 + MediaConfig mediaConfig,
  48 + ZLMediaKitTaskUtils zlMediaKitTaskUtils,
  49 + Map<String, Boolean> startGetMedia) {
  50 + this.zlMediaKitRestFulUtils = zlMediaKitRestFulUtils;
  51 + this.tkMediaServerNodeService = tkMediaServerNodeService;
  52 + this.tkMediaServerService = tkMediaServerService;
  53 + this.mediaConfig = mediaConfig;
  54 + this.zlMediaKitTaskUtils = zlMediaKitTaskUtils;
  55 + this.startGetMedia = startGetMedia;
  56 + }
  57 +
  58 + @PostConstruct
  59 + public void init() {
  60 + updateDefaultMediaServer();
  61 + // 从数据库获取所有的流媒体信息
  62 + List<MediaServerDTO> dtoList = tkMediaServerService.getAllMediaKit();
  63 +
  64 + Optional.ofNullable(dtoList).ifPresent( all ->{
  65 + all.forEach(dto ->{
  66 + String key = dto.getMediaServerId();
  67 + startGetMedia.put(key, true);
  68 + zlMediaKitTaskUtils.startCronTask(
  69 + key,
  70 + () -> {
  71 + ZLMediaKitServerConfig zlMediaKitServerConfig = getMediaServerConfig(dto);
  72 + if (null != zlMediaKitServerConfig) {
  73 + startGetMedia.remove(dto.getMediaServerId());
  74 + zlMediaKitTaskUtils.stop(key);
  75 + zlMediaKitServerConfig.setIp(dto.getIp());
  76 + zlMediaKitServerConfig.setHttpPort(dto.getHttpPort());
  77 + zlMediaKitServerConfig.setTenantId(dto.getTenantId());
  78 + // 进行上线操作
  79 + tkMediaServerNodeService.zlmServerOnline(zlMediaKitServerConfig);
  80 + }
  81 + },
  82 + defaultStateCheckIntervalInSec);
  83 + });
  84 + });
  85 + }
  86 +
  87 + private void updateDefaultMediaServer() {
  88 + // 默认流媒体服务器
  89 + MediaServerDTO oldMediaServer = tkMediaServerService.findDefaultMediaServer();
  90 + MediaServerDTO defaultConfig = mediaConfig.getMediaSerItem();
  91 + if (oldMediaServer == null) {
  92 + defaultConfig.setTenantId(EntityId.NULL_UUID.toString());
  93 + }else{
  94 + defaultConfig.setId(oldMediaServer.getId());
  95 + defaultConfig.setTenantId(oldMediaServer.getTenantId());
  96 + }
  97 + tkMediaServerService.saveOrUpdateMediaServer(defaultConfig);
  98 + }
  99 +
  100 + /**
  101 + * 调用流媒体API接口获取流媒体配置
  102 + * @param mediaServerItem
  103 + * @return
  104 + */
  105 + private ZLMediaKitServerConfig getMediaServerConfig(MediaServerDTO mediaServerItem) {
  106 + log.error("启动流媒体【ZLMedia】验证,流媒体编号【{}】",mediaServerItem.getMediaServerId());
  107 + if (startGetMedia == null) {
  108 + return null;
  109 + }
  110 + if (!mediaServerItem.isDefaultServer()
  111 + && tkMediaServerNodeService.getOne(mediaServerItem.getMediaServerId()).isEmpty()) {
  112 + return null;
  113 + }
  114 + if (startGetMedia.get(mediaServerItem.getMediaServerId()) == null
  115 + || !startGetMedia.get(mediaServerItem.getMediaServerId())) {
  116 + return null;
  117 + }
  118 + JsonNode responseJson = zlMediaKitRestFulUtils.getMediaServerConfig(mediaServerItem);
  119 + if(responseJson == null){
  120 + log.error("流媒体【{}】服务地址【{}:{}】无法访问",mediaServerItem.getMediaServerId(),mediaServerItem.getIp(),mediaServerItem.getHttpPort());
  121 + return null;
  122 + }
  123 + JsonNode data = responseJson.get(FastIotConstants.ZLMediaBody.DATA);
  124 + if (data != null && !data.isEmpty()) {
  125 + log.error("流媒体【{}:{}】调用成功,响应内容【{}】",mediaServerItem.getIp(),mediaServerItem.getHttpPort(),data);
  126 + return JacksonUtil.convertValue(data.get(0), ZLMediaKitServerConfig.class);
  127 + }
  128 + log.error("流媒体【{}:{}】调用失败,失败原因【{}】",mediaServerItem.getIp(),mediaServerItem.getHttpPort(),responseJson.get(FastIotConstants.ZLMediaBody.MSG));
  129 + return null;
  130 + }
  131 +}
  1 +package org.thingsboard.server.utils;
  2 +
  3 +import org.thingsboard.server.common.data.yunteng.enums.AttributeSourceDataTypeEnum;
  4 +import org.thingsboard.server.common.data.yunteng.enums.OperationTypeEnum;
  5 +
  6 +public class ImportModbusUtils {
  7 +
  8 + public static Boolean operationTypeAndOriginalDataTypeStatus(String operationType, String OriginalDataType){
  9 + if((operationType.equals(OperationTypeEnum.inputStatus_r_02.name())||
  10 + operationType.equals(OperationTypeEnum.coilStatus_r_01.name())||
  11 + operationType.equals(OperationTypeEnum.coilStatus_rw_01_05.name())||
  12 + operationType.equals(OperationTypeEnum.coilStatus_rw_01_0F.name())||
  13 + operationType.equals(OperationTypeEnum.coilStatus_w_05.name())||
  14 + operationType.equals(OperationTypeEnum.coilStatus_w_0F.name()))&&!OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())){
  15 + return false;
  16 + }
  17 + if((operationType.equals(OperationTypeEnum.holdingRegister_rw_03_06.name())||
  18 + operationType.equals(OperationTypeEnum.holdingRegister_w_06.name()))&&
  19 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_AB.name())&&
  20 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_BA.name())&&
  21 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_AB.name())&&
  22 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_BA.name())&&
  23 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())&&
  24 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.BITS.name())
  25 + ){
  26 + return false;
  27 + }
  28 + return true;
  29 + }
  30 +
  31 +
  32 + public static String getOriginalDataTypeIsDateType(String OriginalDataType){
  33 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_AB_CD.name())||
  34 + OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_CD_AB.name())||
  35 + OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_BA_DC.name())||
  36 + OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_DC_BA.name())||
  37 + OriginalDataType.equals(AttributeSourceDataTypeEnum.DOUBLE.name())){
  38 + return "DOUBLE";
  39 + }
  40 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())||
  41 + OriginalDataType.equals(AttributeSourceDataTypeEnum.BITS.name())){
  42 + return "BOOLEAN";
  43 + }
  44 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.STRING.name())){
  45 + return "STRING";
  46 + }
  47 + return "INT";
  48 + }
  49 +
  50 + public static String getOriginalDataTypeIsValueRange(String OriginalDataType){
  51 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_AB.name())||
  52 + OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_BA.name())){
  53 + return "\"valueRange\":{\"min\":-32768,\"max\":32767}";
  54 + }
  55 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_AB.name())||
  56 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_BA.name())){
  57 + return "\"valueRange\":{\"min\":0,\"max\":65535}";
  58 + }
  59 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_AB_CD.name())||
  60 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_CD_AB.name())||
  61 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_BA_DC.name())||
  62 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_DC_BA.name())){
  63 + return "\"valueRange\":{\"min\":0,\"max\":4294967295}";
  64 + }
  65 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.STRING.name())){
  66 + return "";
  67 + }
  68 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())||
  69 + OriginalDataType.equals(AttributeSourceDataTypeEnum.BITS.name())){
  70 + return "\"valueRange\":{\"min\":0,\"max\":1}";
  71 + }
  72 + return "\"valueRange\":{\"min\":-2147483648,\"max\":2147483647}";
  73 + }
  74 +
  75 + public static String getOperationTypeIsRw(String operationType){
  76 + String []rw = operationType.split("_");
  77 + return rw[1].equals("r")?"r":"rw";
  78 + }
  79 +
  80 + public static Boolean getOperationTypeIsWriteOnly(String operationType){
  81 + String rw = getOperationTypeIsRw(operationType);
  82 + if(rw.equals("r")){//只读为false
  83 + return false;
  84 + }
  85 + return true;
  86 + }
  87 +}
@@ -451,6 +451,12 @@ caffeine: @@ -451,6 +451,12 @@ caffeine:
451 sceneReact: 451 sceneReact:
452 timeToLiveInMinutes: "${CACHE_SPECS_SCENE_TTL:1440}" 452 timeToLiveInMinutes: "${CACHE_SPECS_SCENE_TTL:1440}"
453 maxSize: "${CACHE_SPECS_SCENE_MAX_SIZE:10000}" 453 maxSize: "${CACHE_SPECS_SCENE_MAX_SIZE:10000}"
  454 + tkSipCacheName:
  455 + timeToLiveInMinutes: "${CACHE_SPECS_TK_SIP_TTL:1440}"
  456 + maxSize: "${CACHE_SPECS_TK_SIP_MAX_SIZE:10000}"
  457 + tkMediaServerCacheName:
  458 + timeToLiveInMinutes: "${CACHE_SPECS_TK_MEDIA_SERVER_TTL:1440}"
  459 + maxSize: "${CACHE_SPECS_TK_MEDIA_SERVER_MAX_SIZE:10000}"
454 redis: 460 redis:
455 # standalone or cluster 461 # standalone or cluster
456 connection: 462 connection:
@@ -476,7 +482,7 @@ redis: @@ -476,7 +482,7 @@ redis:
476 # db index 482 # db index
477 db: "${REDIS_DB:0}" 483 db: "${REDIS_DB:0}"
478 # db password 484 # db password
479 - password: "${REDIS_PASSWORD:}" 485 + password: "${REDIS_PASSWORD:redis@6379}"
480 # pool config 486 # pool config
481 pool_config: 487 pool_config:
482 maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" 488 maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}"
@@ -712,6 +718,9 @@ transport: @@ -712,6 +718,9 @@ transport:
712 # Skip certificate validity check for client certificates. 718 # Skip certificate validity check for client certificates.
713 skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" 719 skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
714 # Local CoAP transport parameters 720 # Local CoAP transport parameters
  721 + gbt28181:
  722 + # Enable/disable gbt28181 transport protocol.
  723 + enabled: "${GBT28181_ENABLED:false}"
715 tcp: 724 tcp:
716 # Enable/disable tcp transport protocol. 725 # Enable/disable tcp transport protocol.
717 enabled: "${TCP_ENABLED:true}" 726 enabled: "${TCP_ENABLED:true}"
@@ -1218,3 +1227,45 @@ logging: @@ -1218,3 +1227,45 @@ logging:
1218 frp: 1227 frp:
1219 server: 1228 server:
1220 address: ${FRP_SERVER_ADDRESS:http://127.0.0.1} 1229 address: ${FRP_SERVER_ADDRESS:http://127.0.0.1}
  1230 +sip:
  1231 + # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡,
  1232 + # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4
  1233 + # 如果不明白,就使用0.0.0.0,大部分情况都是可以的
  1234 + # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。
  1235 + ip: ${GBT28181_SIP_IP:127.0.0.1}
  1236 + # [可选] 28181服务监听的端口
  1237 + port: ${GBT28181_SIP_PORT:5060}
  1238 + #[可选]
  1239 + id: ${GBT28181_SIP_ID:51010700599000000001}
  1240 + # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
  1241 + # 后两位为行业编码,定义参照附录D.3
  1242 + # 标识四川成都武侯下区 信息行业接入
  1243 + # [可选]
  1244 + domain: ${GBT28181_SIP_DOMAIN:5101070059}
  1245 + #[可选]
  1246 + password: ${GBT28181_SIP_PASSWORD:61332286}
  1247 +#zlm 默认服务器配置
  1248 +media:
  1249 + id: ${GBT28181_MEDIA_GENERAL_ID:D2okJWKKaQ5bX7Va}
  1250 + # [必须修改] zlm服务器的内网IP
  1251 + ip: ${GBT28181_MEDIA_IP:127.0.0.1}
  1252 + # [必须修改] zlm服务器的http.port
  1253 + http-port: ${GBT28181_MEDIA_HTTP_PORT:28080}
  1254 + # [可选] zlm服务器的hook.admin_params=secret
  1255 + secret: ${GBT28181_MEDIA_API_SECRET:QhrTN7k6HcDnt0YyeolwHwiVYDgIHPMZ}
  1256 + # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
  1257 + rtp:
  1258 + # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
  1259 + enable: true
  1260 + # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功
  1261 + port-range: ${GBT28181_MEDIA_RTP_PORT_RANGE:30000,30500} # 端口范围
  1262 + # [可选] 国标级联在此范围内选择端口发送媒体流,
  1263 + send-port-range: ${GBT28181_MEDIA_RTP_PORT_RANGE:30000,30500} # 端口范围
  1264 + # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
  1265 + record-assist-port: 0
  1266 + defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:10}"
  1267 +
  1268 +thingskit:
  1269 + release:
  1270 + version: v1.1.1 Release
  1271 + date: 20230630
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 * http://www.apache.org/licenses/LICENSE-2.0 8 * http://www.apache.org/licenses/LICENSE-2.0
9 * 9 *
10 * Unless required by applicable law or agreed to in writing, software 10 * Unless required by applicable law or agreed to in writing, software
11 - * distributed under the License is distributed on an "AS IS" BASIS, 11 + * distributed under the License is distributed on an "AS IS" BASIS,in
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
@@ -130,10 +130,7 @@ message SessionEventMsg { @@ -130,10 +130,7 @@ message SessionEventMsg {
130 SessionEvent event = 2; 130 SessionEvent event = 2;
131 } 131 }
132 132
133 -//thingskit  
134 -message PostEventMsg {  
135 - repeated KeyValueProto kv = 1;  
136 -} 133 +
137 message PostTelemetryMsg { 134 message PostTelemetryMsg {
138 repeated TsKvListProto tsKvList = 1; 135 repeated TsKvListProto tsKvList = 1;
139 } 136 }
@@ -682,6 +679,7 @@ message TransportApiRequestMsg { @@ -682,6 +679,7 @@ message TransportApiRequestMsg {
682 679
683 //Thingskit function 680 //Thingskit function
684 ScriptProto script = 14; 681 ScriptProto script = 14;
  682 + Gbt28181RequestMsg gbt28181RequestMsg = 15;
685 } 683 }
686 684
687 /* Response from ThingsBoard Core Service to Transport Service */ 685 /* Response from ThingsBoard Core Service to Transport Service */
@@ -699,6 +697,7 @@ message TransportApiResponseMsg { @@ -699,6 +697,7 @@ message TransportApiResponseMsg {
699 697
700 //Thingskit function 698 //Thingskit function
701 repeated ScriptProto scriptsResponseMsg = 11; 699 repeated ScriptProto scriptsResponseMsg = 11;
  700 + Gbt28181ResponseMsg gbt28181ResponseMsg = 12;
702 } 701 }
703 702
704 /* Messages that are handled by ThingsBoard Core Service */ 703 /* Messages that are handled by ThingsBoard Core Service */
@@ -795,3 +794,37 @@ message ScriptProto{ @@ -795,3 +794,37 @@ message ScriptProto{
795 string convertJs = 6; 794 string convertJs = 6;
796 int32 status = 7; 795 int32 status = 7;
797 } 796 }
  797 +//thingskit function 设备上报的物模型事件
  798 +message PostEventMsg {
  799 + repeated KeyValueProto kv = 1;
  800 +}
  801 +message Gbt28181RequestMsg{
  802 + bytes context = 1;
  803 + string contextType = 2;
  804 + string callId = 3;
  805 + string fromTag = 4;
  806 + string toTag = 5;
  807 + string viaTag = 6;
  808 + string viaBranch = 7;
  809 + int32 sn = 8;
  810 + int64 tenantIdMSB = 9;
  811 + int64 tenantIdLSB = 10;
  812 + int64 entityIdMSB = 11;
  813 + int64 entityIdLSB = 12;
  814 +}
  815 +message Gbt28181ResponseMsg{
  816 + bytes context = 1;
  817 + string contextType = 2;
  818 + string callId = 3;
  819 + string fromTag = 4;
  820 + string toTag = 5;
  821 + string viaTag = 6;
  822 + string viaBranch = 7;
  823 + int32 sn = 8;
  824 + int64 tenantIdMSB = 9;
  825 + int64 tenantIdLSB = 10;
  826 + int64 entityIdMSB = 11;
  827 + int64 entityIdLSB = 12;
  828 +}
  829 +
  830 +
@@ -48,6 +48,8 @@ public class DataConstants { @@ -48,6 +48,8 @@ public class DataConstants {
48 public static final String HTTP_TRANSPORT_NAME = "HTTP"; 48 public static final String HTTP_TRANSPORT_NAME = "HTTP";
49 public static final String SNMP_TRANSPORT_NAME = "SNMP"; 49 public static final String SNMP_TRANSPORT_NAME = "SNMP";
50 50
  51 + public static final String GBT_28181 = "GBT28181";
  52 +
51 53
52 public static final String[] allScopes() { 54 public static final String[] allScopes() {
53 return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE}; 55 return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE};
@@ -21,5 +21,6 @@ public enum DeviceTransportType { @@ -21,5 +21,6 @@ public enum DeviceTransportType {
21 TCP, 21 TCP,
22 COAP, 22 COAP,
23 LWM2M, 23 LWM2M,
24 - SNMP 24 + SNMP,
  25 + GBT28181
25 } 26 }
@@ -34,6 +34,7 @@ import java.io.Serializable; @@ -34,6 +34,7 @@ import java.io.Serializable;
34 @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"), 34 @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"),
35 @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), 35 @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"),
36 @JsonSubTypes.Type(value = TkTcpDeviceTransportConfiguration.class, name = "TCP"), 36 @JsonSubTypes.Type(value = TkTcpDeviceTransportConfiguration.class, name = "TCP"),
  37 + @JsonSubTypes.Type(value = TkGBTDeviceTransportConfiguration.class, name = "GBT28181"),
37 @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"), 38 @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"),
38 @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), 39 @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"),
39 @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")}) 40 @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")})
  1 +/**
  2 + * Copyright © 2016-2022 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.device.data;
  17 +import lombok.Data;
  18 +import org.thingsboard.server.common.data.DeviceTransportType;
  19 +
  20 +@Data
  21 +public class TkGBTDeviceTransportConfiguration implements DeviceTransportConfiguration {
  22 + @Override
  23 + public DeviceTransportType getType() {
  24 + return DeviceTransportType.GBT28181;
  25 + }
  26 +
  27 +}
@@ -32,6 +32,7 @@ import java.io.Serializable; @@ -32,6 +32,7 @@ import java.io.Serializable;
32 @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), 32 @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"),
33 @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), 33 @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"),
34 @JsonSubTypes.Type(value = TkTcpDeviceProfileTransportConfiguration.class, name = "TCP"), 34 @JsonSubTypes.Type(value = TkTcpDeviceProfileTransportConfiguration.class, name = "TCP"),
  35 + @JsonSubTypes.Type(value = TkGBT28181DeviceProfileTransportConfiguration.class, name = "GBT28181"),
35 @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), 36 @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"),
36 @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"), 37 @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"),
37 @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP") 38 @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP")
  1 +package org.thingsboard.server.common.data.device.profile;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.DeviceTransportType;
  5 +
  6 +@Data
  7 +public class TkGBT28181DeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
  8 + @Override
  9 + public DeviceTransportType getType() {
  10 + return DeviceTransportType.GBT28181;
  11 + }
  12 +
  13 +
  14 +}
@@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.device.profile; @@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.device.profile;
3 import lombok.Data; 3 import lombok.Data;
4 import org.thingsboard.server.common.data.DeviceTransportType; 4 import org.thingsboard.server.common.data.DeviceTransportType;
5 import org.thingsboard.server.common.data.validation.NoXss; 5 import org.thingsboard.server.common.data.validation.NoXss;
  6 +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum;
6 7
7 @Data 8 @Data
8 public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { 9 public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
@@ -26,6 +27,12 @@ public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTr @@ -26,6 +27,12 @@ public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTr
26 @NoXss 27 @NoXss
27 private String downScriptId; 28 private String downScriptId;
28 29
  30 + /**
  31 + * 协议: 默认自定义
  32 + */
  33 + @NoXss
  34 + private ProtocolAnalysisEnum protocol = ProtocolAnalysisEnum.CUSTOM;
  35 +
29 @Override 36 @Override
30 public DeviceTransportType getType() { 37 public DeviceTransportType getType() {
31 return DeviceTransportType.TCP; 38 return DeviceTransportType.TCP;
1 package org.thingsboard.server.common.data.yunteng.common; 1 package org.thingsboard.server.common.data.yunteng.common;
2 2
3 import org.thingsboard.server.common.data.yunteng.dto.SysDictItemDTO; 3 import org.thingsboard.server.common.data.yunteng.dto.SysDictItemDTO;
  4 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
4 5
5 public interface TkCommonService { 6 public interface TkCommonService {
6 /** 7 /**
@@ -11,6 +12,4 @@ public interface TkCommonService { @@ -11,6 +12,4 @@ public interface TkCommonService {
11 * @return 返回字典Item表 12 * @return 返回字典Item表
12 */ 13 */
13 SysDictItemDTO getDictValueByCodeAndText(String dictCode, String codeValue); 14 SysDictItemDTO getDictValueByCodeAndText(String dictCode, String codeValue);
14 -  
15 -  
16 } 15 }
  1 +package org.thingsboard.server.common.data.yunteng.common.media;
  2 +
  3 +public interface CommonCallback<T>{
  4 + void run(T t);
  5 +}
  1 +package org.thingsboard.server.common.data.yunteng.common.media;
  2 +
  3 +import java.util.*;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  7 +import org.springframework.stereotype.Component;
  8 +import org.thingsboard.server.common.data.yunteng.config.media.UserSetting;
  9 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  10 +import org.thingsboard.server.common.data.yunteng.core.cache.CacheUtils;
  11 +import org.thingsboard.server.common.data.yunteng.dto.sip.SipMessageHeaderDTO;
  12 +import org.thingsboard.server.common.data.yunteng.dto.sip.SsrcTransactionDTO;
  13 +import org.thingsboard.server.common.data.yunteng.enums.SessionTypeEnum;
  14 +
  15 +/** 视频流session管理器,管理视频预览、预览回放的通信句柄 */
  16 +@Component
  17 +@Slf4j
  18 +public class VideoStreamSessionManager {
  19 +
  20 + @Autowired private UserSetting userSetting;
  21 + @Autowired private CacheUtils cacheUtils;
  22 + private final String cacheName = FastIotConstants.MediaServerKey.MEDIA_SERVER_CACHE_NAME;
  23 +
  24 + /**
  25 + * 添加一个点播/回放的事务信息 后续可以通过流Id/callID
  26 + *
  27 + * @param cameraCode 设备ID
  28 + * @param channelId 通道ID
  29 + * @param callId 一次请求的CallID
  30 + * @param stream 流名称
  31 + * @param mediaServerId 所使用的流媒体ID
  32 + */
  33 + public void put(
  34 + String cameraCode,
  35 + String channelId,
  36 + String callId,
  37 + String stream,
  38 + String ssrc,
  39 + SipMessageHeaderDTO sipMessage,
  40 + String mediaServerId,
  41 + SessionTypeEnum type) {
  42 +
  43 + SsrcTransactionDTO ssrcTransaction = new SsrcTransactionDTO();
  44 + ssrcTransaction.setCameraCode(cameraCode);
  45 + ssrcTransaction.setChannelId(channelId);
  46 + ssrcTransaction.setStream(stream);
  47 + ssrcTransaction.setSipTransactionInfo(sipMessage);
  48 + ssrcTransaction.setCallId(callId);
  49 + ssrcTransaction.setSsrc(ssrc);
  50 + ssrcTransaction.setMediaServerId(mediaServerId);
  51 + ssrcTransaction.setType(type);
  52 + String fullKey = buildTransactionFullKey(cameraCode, channelId, callId, stream);
  53 + String channelKey = buildTransactionChannelKey(cameraCode, channelId, stream);
  54 + String streamKey = buildTransactionStreamKey(stream);
  55 + String cameraKey = buildTransactionCameraKey(cameraCode);
  56 +
  57 + freshTransactionKey(cameraKey, fullKey, false);
  58 + freshTransactionKey(streamKey, fullKey, false);
  59 + freshTransactionKey(channelKey, fullKey, false);
  60 + cacheUtils.put(cacheName, fullKey, ssrcTransaction);
  61 + }
  62 +
  63 + private void freshTransactionKey(String key, String val, boolean remove) {
  64 + Optional<Set<String>> cameraDatas = cacheUtils.get(cacheName, key);
  65 + Set<String> values = new HashSet<>();
  66 + cameraDatas.ifPresent(values::addAll);
  67 + if (remove) {
  68 + values.remove(val);
  69 + } else {
  70 + values.add(val);
  71 + }
  72 + cacheUtils.put(cacheName, key, values);
  73 + }
  74 +
  75 + public Optional<Set<String>> getSsrcTransactionCamaraKey(String cameraCode) {
  76 + return cacheUtils.get(cacheName, buildTransactionCameraKey(cameraCode));
  77 + }
  78 +
  79 + public Optional<Set<String>> getSsrcTransactionStreamKey(String stream) {
  80 + return cacheUtils.get(cacheName, buildTransactionStreamKey(stream));
  81 + }
  82 +
  83 + public Optional<Set<String>> getSsrcTransactionChannelKey(
  84 + String cameraCode, String channelId, String stream) {
  85 + return cacheUtils.get(cacheName, buildTransactionChannelKey(cameraCode, channelId, stream));
  86 + }
  87 +
  88 + public Optional<SsrcTransactionDTO> getSsrcTransaction(String fullKey) {
  89 + return cacheUtils.get(cacheName, fullKey);
  90 + }
  91 + public Optional<SsrcTransactionDTO> getSsrcTransaction(String cameraCode, String channelId, String stream) {
  92 + Optional<Set<String>> keys =getSsrcTransactionChannelKey(cameraCode, channelId,stream);
  93 +
  94 + if(keys.isPresent()){
  95 + Optional<String> fullKey =keys.get().stream().findFirst();
  96 + if(fullKey.isPresent()){
  97 + return getSsrcTransaction(fullKey.get());
  98 + }
  99 + }
  100 + return Optional.empty();
  101 + }
  102 +
  103 + public String getMediaServerId(String deviceId, String channelId, String stream) {
  104 + Optional<Set<String>> channels = getSsrcTransactionChannelKey(deviceId, channelId, stream);
  105 + if (channels.isPresent()) {
  106 + for (String fullKey : channels.get()) {
  107 + Optional<SsrcTransactionDTO> result = getSsrcTransaction(fullKey);
  108 + if (result.isPresent()) {
  109 + return result.get().getMediaServerId();
  110 + }
  111 + }
  112 + }
  113 + return null;
  114 + }
  115 +
  116 + public String getSSRC(String deviceId, String channelId, String stream) {
  117 + Optional<Set<String>> channels = getSsrcTransactionChannelKey(deviceId, channelId, stream);
  118 +
  119 + if (channels.isPresent()) {
  120 + for (String fullKey : channels.get()) {
  121 + Optional<SsrcTransactionDTO> result = getSsrcTransaction(fullKey);
  122 + if (result.isPresent()) {
  123 + return result.get().getSsrc();
  124 + }
  125 + }
  126 + }
  127 + return null;
  128 + }
  129 +
  130 + public void remove(String cameraCode, String channelId, String stream) {
  131 + Optional<Set<String>> channelKeys = getSsrcTransactionChannelKey(cameraCode, channelId, stream);
  132 + channelKeys.ifPresent(
  133 + keys -> {
  134 + keys.forEach(
  135 + fullKey -> {
  136 + String streamKey = buildTransactionStreamKey(stream);
  137 + String cameraKey = buildTransactionCameraKey(cameraCode);
  138 + String channelKey = buildTransactionChannelKey(cameraCode, channelId, stream);
  139 + freshTransactionKey(cameraKey, fullKey, true);
  140 + freshTransactionKey(streamKey, fullKey, true);
  141 + freshTransactionKey(channelKey, fullKey, true);
  142 + cacheUtils.invalidate(cacheName, fullKey);
  143 + });
  144 + });
  145 + }
  146 +
  147 + public String buildTransactionFullKey(
  148 + String cameraCode, String channelId, String callId, String stream) {
  149 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  150 + + userSetting.getServerId()
  151 + + "_"
  152 + + cameraCode
  153 + + "_"
  154 + + channelId
  155 + + "_"
  156 + + callId
  157 + + "_"
  158 + + stream;
  159 + }
  160 +
  161 + public String buildTransactionChannelKey(String cameraCode, String channelId, String stream) {
  162 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  163 + + userSetting.getServerId()
  164 + + "_"
  165 + + cameraCode
  166 + + "_"
  167 + + channelId
  168 + + "_"
  169 + + stream;
  170 + }
  171 +
  172 + public String buildTransactionStreamKey(String stream) {
  173 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  174 + + userSetting.getServerId()
  175 + + "_"
  176 + + stream;
  177 + }
  178 +
  179 + public String buildTransactionCameraKey(String cameraCode) {
  180 + return FastIotConstants.MediaServerKey.MEDIA_TRANSACTION_USED_PREFIX
  181 + + userSetting.getServerId()
  182 + + "_"
  183 + + cameraCode;
  184 + }
  185 +
  186 + public String getReceiveRtpKey(String ssrcStream) {
  187 + return FastIotConstants.CacheSipKey.TK_OTHER_RECEIVE_RTP_INFO
  188 + + userSetting.getServerId()
  189 + + "_"
  190 + + ssrcStream;
  191 + }
  192 +
  193 + public String getReceivePsKey(String ssrcStream) {
  194 + return FastIotConstants.CacheSipKey.TK_OTHER_RECEIVE_PS_INFO
  195 + + userSetting.getServerId()
  196 + + "_"
  197 + + ssrcStream;
  198 + }
  199 +}
  1 +package org.thingsboard.server.common.data.yunteng.common.media;
  2 +
  3 +import java.time.Instant;
  4 +import java.time.LocalDateTime;
  5 +import java.time.ZoneId;
  6 +import java.util.*;
  7 +import java.util.concurrent.ConcurrentHashMap;
  8 +import java.util.concurrent.TimeUnit;
  9 +
  10 +import com.fasterxml.jackson.databind.JsonNode;
  11 +import com.fasterxml.jackson.databind.node.ObjectNode;
  12 +import org.springframework.scheduling.annotation.Scheduled;
  13 +import org.springframework.stereotype.Component;
  14 +import org.springframework.util.CollectionUtils;
  15 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
  16 +import org.thingsboard.server.common.data.yunteng.dto.sip.hook.IHookSubscribe;
  17 +import org.thingsboard.server.common.data.yunteng.enums.HookTypeEnum;
  18 +
  19 +/**
  20 + * ZLMediaServer的hook事件订阅
  21 + *
  22 + * @author lin
  23 + */
  24 +@Component
  25 +public class ZlmHttpHookSubscribe {
  26 +
  27 + @FunctionalInterface
  28 + public interface Event {
  29 + void response(MediaServerDTO mediaServerInfo, JsonNode response);
  30 + }
  31 +
  32 + private Map<HookTypeEnum, Map<IHookSubscribe, Event>> allSubscribes = new ConcurrentHashMap<>();
  33 +
  34 + public void addSubscribe(IHookSubscribe hookSubscribe, Event event) {
  35 + if (hookSubscribe.getExpires() == null) {
  36 + // 默认5分钟过期
  37 + Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(5));
  38 + hookSubscribe.setExpires(LocalDateTime.ofInstant(expiresInstant, ZoneId.systemDefault()));
  39 + }
  40 + allSubscribes
  41 + .computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>())
  42 + .put(hookSubscribe, event);
  43 + }
  44 +
  45 + public Event sendNotify(HookTypeEnum type, JsonNode hookResponse) {
  46 + Event event = null;
  47 + Map<IHookSubscribe, Event> eventMap = allSubscribes.get(type);
  48 + if (eventMap == null) {
  49 + return null;
  50 + }
  51 + for (IHookSubscribe key : eventMap.keySet()) {
  52 + Boolean result = null;
  53 + // 使用 fields() 方法获取键值对迭代器
  54 + Iterator<Map.Entry<String, JsonNode>> fieldsIterator = key.getContent().fields();
  55 + while (fieldsIterator.hasNext()) {
  56 + Map.Entry<String, JsonNode> field = fieldsIterator.next();
  57 + String s = field.getKey();
  58 + JsonNode value = field.getValue();
  59 + if (result == null) {
  60 + result = value.equals(hookResponse.get(s));
  61 + } else {
  62 + if (value == null) {
  63 + continue;
  64 + }
  65 + result = result && value.equals(hookResponse.get(s));
  66 + }
  67 + }
  68 + if (null != result && result) {
  69 + event = eventMap.get(key);
  70 + }
  71 + }
  72 + return event;
  73 + }
  74 +
  75 + public void removeSubscribe(IHookSubscribe hookSubscribe) {
  76 + Map<IHookSubscribe, Event> eventMap = allSubscribes.get(hookSubscribe.getHookType());
  77 + if (eventMap == null) {
  78 + return;
  79 + }
  80 +
  81 + Set<Map.Entry<IHookSubscribe, Event>> entries = eventMap.entrySet();
  82 + if (entries.size() > 0) {
  83 + List<Map.Entry<IHookSubscribe, Event>> entriesToRemove = new ArrayList<>();
  84 + for (Map.Entry<IHookSubscribe, Event> entry : entries) {
  85 + ObjectNode content = entry.getKey().getContent();
  86 + if (content == null || content.size() == 0) {
  87 + entriesToRemove.add(entry);
  88 + continue;
  89 + }
  90 + Boolean result = null;
  91 + Iterator<Map.Entry<String, JsonNode>> fieldsIterator = content.fields();
  92 + while (fieldsIterator.hasNext()) {
  93 + Map.Entry<String, JsonNode> field = fieldsIterator.next();
  94 + String s = field.getKey();
  95 + JsonNode value = field.getValue();
  96 + if (result == null) {
  97 + result = value.equals(hookSubscribe.getContent().get(s));
  98 + } else {
  99 + if (value == null) {
  100 + continue;
  101 + }
  102 + result = result && value.equals(hookSubscribe.getContent().get(s));
  103 + }
  104 + }
  105 + if (result) {
  106 + entriesToRemove.add(entry);
  107 + }
  108 + }
  109 +
  110 + if (!CollectionUtils.isEmpty(entriesToRemove)) {
  111 + for (Map.Entry<IHookSubscribe, Event> entry : entriesToRemove) {
  112 + entries.remove(entry);
  113 + }
  114 + }
  115 + }
  116 + }
  117 +
  118 + /**
  119 + * 获取某个类型的所有的订阅
  120 + *
  121 + * @param type
  122 + * @return
  123 + */
  124 + public List<Event> getSubscribes(HookTypeEnum type) {
  125 + Map<IHookSubscribe, Event> eventMap = allSubscribes.get(type);
  126 + if (eventMap == null) {
  127 + return null;
  128 + }
  129 + List<Event> result = new ArrayList<>();
  130 + for (IHookSubscribe key : eventMap.keySet()) {
  131 + result.add(eventMap.get(key));
  132 + }
  133 + return result;
  134 + }
  135 +
  136 + public List<IHookSubscribe> getAll() {
  137 + ArrayList<IHookSubscribe> result = new ArrayList<>();
  138 + Collection<Map<IHookSubscribe, Event>> values = allSubscribes.values();
  139 + for (Map<IHookSubscribe, Event> value : values) {
  140 + result.addAll(value.keySet());
  141 + }
  142 + return result;
  143 + }
  144 +
  145 + /** 对订阅数据进行过期清理 */
  146 + @Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行一次
  147 + public void execute() {
  148 +
  149 + Instant instant = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(5));
  150 + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
  151 + int total = 0;
  152 + for (HookTypeEnum hookType : allSubscribes.keySet()) {
  153 + Map<IHookSubscribe, Event> hookSubscribeEventMap = allSubscribes.get(hookType);
  154 + if (hookSubscribeEventMap.size() > 0) {
  155 + for (IHookSubscribe hookSubscribe : hookSubscribeEventMap.keySet()) {
  156 + if (hookSubscribe.getExpires().isBefore(localDateTime)) {
  157 + // 过期的
  158 + hookSubscribeEventMap.remove(hookSubscribe);
  159 + total++;
  160 + }
  161 + }
  162 + }
  163 + }
  164 + }
  165 +}
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.beans.factory.annotation.Value;
  6 +import org.springframework.boot.context.properties.ConfigurationProperties;
  7 +import org.springframework.stereotype.Component;
  8 +import org.springframework.util.ObjectUtils;
  9 +import org.thingsboard.server.common.data.yunteng.dto.sip.MediaServerDTO;
  10 +
  11 +import java.net.InetAddress;
  12 +import java.net.UnknownHostException;
  13 +import java.util.Objects;
  14 +import java.util.regex.Pattern;
  15 +
  16 +/**
  17 + * 配置文件里面的流媒体配置信息
  18 + */
  19 +@ConfigurationProperties(prefix = "media")
  20 +@Component
  21 +@Data
  22 +@Slf4j
  23 +public class MediaConfig {
  24 + @Value("${media.id}")
  25 + private String mediaServerId;
  26 +
  27 + @Value("${media.ip}")
  28 + private String ip;
  29 +
  30 + @Value("${media.hook-ip:}")
  31 + private String hookIp;
  32 +
  33 + @Value("${sip.ip}")
  34 + private String sipIp;
  35 +
  36 + @Value("${sip.domain}")
  37 + private String sipDomain;
  38 +
  39 + @Value("${media.sdp-ip:${media.ip}}")
  40 + private String sdpIp;
  41 +
  42 + @Value("${media.stream-ip:${media.ip}}")
  43 + private String streamIp;
  44 +
  45 + @Value("${media.http-port}")
  46 + private Integer httpPort;
  47 +
  48 + @Value("${media.http-ssl-port:0}")
  49 + private Integer httpSslPort = 0;
  50 +
  51 + @Value("${media.rtmp-port:0}")
  52 + private Integer rtmpPort = 0;
  53 +
  54 + @Value("${media.rtmp-ssl-port:0}")
  55 + private Integer rtmpSslPort = 0;
  56 +
  57 + @Value("${media.rtp-proxy-port:0}")
  58 + private Integer rtpProxyPort = 0;
  59 +
  60 + @Value("${media.rtsp-port:0}")
  61 + private Integer rtspPort = 0;
  62 +
  63 + @Value("${media.rtsp-ssl-port:0}")
  64 + private Integer rtspSslPort = 0;
  65 +
  66 + @Value("${media.auto-config:true}")
  67 + private boolean autoConfig = true;
  68 +
  69 + @Value("${media.secret}")
  70 + private String secret;
  71 +
  72 + @Value("${media.rtp.enable}")
  73 + private boolean rtpEnable;
  74 +
  75 + @Value("${media.rtp.port-range}")
  76 + private String rtpPortRange;
  77 +
  78 + @Value("${media.rtp.send-port-range}")
  79 + private String rtpSendPortRange;
  80 +
  81 + @Value("${media.record-assist-port:0}")
  82 + private Integer recordAssistPort = 0;
  83 + private boolean status;
  84 +
  85 + public MediaServerDTO getMediaSerItem() {
  86 + MediaServerDTO mediaServerItem = new MediaServerDTO();
  87 + mediaServerItem.setMediaServerId(mediaServerId);
  88 + mediaServerItem.setIp(ip);
  89 + mediaServerItem.setDefaultServer(true);
  90 + mediaServerItem.setHookIp(getHookIp());
  91 + mediaServerItem.setSdpIp(getSdpIp());
  92 + mediaServerItem.setStreamIp(getStreamIp());
  93 + mediaServerItem.setHttpPort(httpPort);
  94 + mediaServerItem.setHttpSslPort(httpSslPort);
  95 + mediaServerItem.setRtmpPort(rtmpPort);
  96 + mediaServerItem.setRtmpSslPort(rtmpSslPort);
  97 + mediaServerItem.setRtpProxyPort(getRtpProxyPort());
  98 + mediaServerItem.setRtspPort(rtspPort);
  99 + mediaServerItem.setRtspSslPort(rtspSslPort);
  100 + mediaServerItem.setAutoConfig(autoConfig);
  101 + mediaServerItem.setSecret(secret);
  102 + mediaServerItem.setRtpEnable(rtpEnable);
  103 + mediaServerItem.setRtpPortRange(rtpPortRange);
  104 + mediaServerItem.setSendRtpPortRange(rtpSendPortRange);
  105 + mediaServerItem.setRecordAssistPort(recordAssistPort);
  106 + mediaServerItem.setHookAliveInterval(30.00f);
  107 + mediaServerItem.setStatus(status);
  108 + return mediaServerItem;
  109 + }
  110 +
  111 + public String getHookIp() {
  112 + if (ObjectUtils.isEmpty(hookIp)) {
  113 + return sipIp.split(",")[0];
  114 + } else {
  115 + return hookIp;
  116 + }
  117 + }
  118 +
  119 + public String getSipIp() {
  120 + if (sipIp == null) {
  121 + return this.ip;
  122 + } else {
  123 + return sipIp;
  124 + }
  125 + }
  126 +
  127 + public int getRtpProxyPort() {
  128 + return Objects.requireNonNullElse(rtpProxyPort, 0);
  129 + }
  130 +
  131 + public String getSdpIp() {
  132 + if (ObjectUtils.isEmpty(sdpIp)) {
  133 + return ip;
  134 + } else {
  135 + if (isValidIPAddress(sdpIp)) {
  136 + return sdpIp;
  137 + } else {
  138 + // 按照域名解析
  139 + String hostAddress = null;
  140 + try {
  141 + hostAddress = InetAddress.getByName(sdpIp).getHostAddress();
  142 + } catch (UnknownHostException e) {
  143 + log.error("[获取SDP IP]: 域名解析失败");
  144 + }
  145 + return hostAddress;
  146 + }
  147 + }
  148 + }
  149 +
  150 + public String getStreamIp() {
  151 + if (ObjectUtils.isEmpty(streamIp)) {
  152 + return ip;
  153 + } else {
  154 + return streamIp;
  155 + }
  156 + }
  157 +
  158 + private boolean isValidIPAddress(String ipAddress) {
  159 + if ((ipAddress != null) && (!ipAddress.isEmpty())) {
  160 + return Pattern.matches(
  161 + "^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$",
  162 + ipAddress);
  163 + }
  164 + return false;
  165 + }
  166 +}
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +@ConfigurationProperties(prefix = "sip")
  8 +@Component
  9 +@Data
  10 +public class SipConfig {
  11 + private String ip;
  12 + private String showIp;
  13 + private Integer port;
  14 + private String id;
  15 + private String domain;
  16 + private String password;
  17 + Integer ptzSpeed = 50;
  18 + Integer registerTimeInterval = 120;
  19 + private boolean alarm;
  20 +
  21 + public String getShowIp() {
  22 + if (this.showIp == null) {
  23 + return this.ip;
  24 + }
  25 + return showIp;
  26 + }
  27 +}
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.beans.factory.annotation.Value;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +@Component
  8 +@Data
  9 +public class ThingsKitVersionConfig {
  10 + @Value("${thingskit.release.version}")
  11 + private String releaseVersion;
  12 +
  13 + @Value("${thingskit.release.date}")
  14 + private String releaseDate;
  15 +}
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +@ConfigurationProperties(prefix = "user-settings")
  8 +@Component
  9 +@Data
  10 +public class UserSetting {
  11 + private String serverId = "000000";
  12 + private Boolean seniorSdp = Boolean.FALSE;
  13 + private Boolean pushAuthority = Boolean.TRUE;
  14 + private Boolean recordSip = Boolean.TRUE;
  15 + private Boolean recordPushLive = Boolean.TRUE;
  16 + private String recordPath = null;
  17 + private Boolean streamOnDemand = Boolean.TRUE;
  18 + private Boolean sipUseSourceIpAsRemoteAddress = Boolean.FALSE;
  19 + private Integer playTimeout = 30000;
  20 +}
  1 +package org.thingsboard.server.common.data.yunteng.config.media;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +/** ZLMediaKit流媒体配置文件,具体配置说明可以参考ZLMediaKit的config.ini文件 */
  7 +@Data
  8 +public class ZLMediaKitServerConfig {
  9 + @JsonProperty(value = "api.apiDebug")
  10 + private String apiDebug;
  11 +
  12 + @JsonProperty(value = "api.secret")
  13 + private String apiSecret;
  14 +
  15 + @JsonProperty(value = "api.snapRoot")
  16 + private String apiSnapRoot;
  17 +
  18 + @JsonProperty(value = "api.defaultSnap")
  19 + private String apiDefaultSnap;
  20 +
  21 + @JsonProperty(value = "ffmpeg.bin")
  22 + private String ffmpegBin;
  23 +
  24 + @JsonProperty(value = "ffmpeg.cmd")
  25 + private String ffmpegCmd;
  26 +
  27 + @JsonProperty(value = "ffmpeg.snap")
  28 + private String ffmpegSnap;
  29 +
  30 + @JsonProperty(value = "ffmpeg.log")
  31 + private String ffmpegLog;
  32 +
  33 + @JsonProperty(value = "ffmpeg.restart_sec")
  34 + private String ffmpegRestartSec;
  35 +
  36 + @JsonProperty(value = "protocol.modify_stamp")
  37 + private String protocolModifyStamp;
  38 +
  39 + @JsonProperty(value = "protocol.enable_audio")
  40 + private String protocolEnableAudio;
  41 +
  42 + @JsonProperty(value = "protocol.add_mute_audio")
  43 + private String protocolAddMuteAudio;
  44 +
  45 + @JsonProperty(value = "protocol.continue_push_ms")
  46 + private String protocolContinuePushMs;
  47 +
  48 + @JsonProperty(value = "protocol.enable_hls")
  49 + private String protocolEnableHls;
  50 +
  51 + @JsonProperty(value = "protocol.enable_mp4")
  52 + private String protocolEnableMp4;
  53 +
  54 + @JsonProperty(value = "protocol.enable_rtsp")
  55 + private String protocolEnableRtsp;
  56 +
  57 + @JsonProperty(value = "protocol.enable_rtmp")
  58 + private String protocolEnableRtmp;
  59 +
  60 + @JsonProperty(value = "protocol.enable_ts")
  61 + private String protocolEnableTs;
  62 +
  63 + @JsonProperty(value = "protocol.enable_fmp4")
  64 + private String protocolEnableFmp4;
  65 +
  66 + @JsonProperty(value = "protocol.mp4_as_player")
  67 + private String protocolMp4AsPlayer;
  68 +
  69 + @JsonProperty(value = "protocol.mp4_max_second")
  70 + private String protocolMp4MaxSecond;
  71 +
  72 + @JsonProperty(value = "protocol.mp4_save_path")
  73 + private String protocolMp4SavePath;
  74 +
  75 + @JsonProperty(value = "protocol.hls_save_path")
  76 + private String protocolHlsSavePath;
  77 +
  78 + @JsonProperty(value = "protocol.hls_demand")
  79 + private String protocolHlsDemand;
  80 +
  81 + @JsonProperty(value = "protocol.rtsp_demand")
  82 + private String protocolRtspDemand;
  83 +
  84 + @JsonProperty(value = "protocol.rtmp_demand")
  85 + private String protocolRtmpDemand;
  86 +
  87 + @JsonProperty(value = "protocol.ts_demand")
  88 + private String protocolTsDemand;
  89 +
  90 + @JsonProperty(value = "protocol.fmp4_demand")
  91 + private String protocolFmp4Demand;
  92 +
  93 + @JsonProperty(value = "general.enableVhost")
  94 + private String generalEnableVhost;
  95 +
  96 + @JsonProperty(value = "general.flowThreshold")
  97 + private String generalFlowThreshold;
  98 +
  99 + @JsonProperty(value = "general.maxStreamWaitMS")
  100 + private String generalMaxStreamWaitMS;
  101 +
  102 + @JsonProperty(value = "general.streamNoneReaderDelayMS")
  103 + private int generalStreamNoneReaderDelayMS;
  104 +
  105 + @JsonProperty(value = "general.resetWhenRePlay")
  106 + private String generalResetWhenRePlay;
  107 +
  108 + @JsonProperty(value = "general.mergeWriteMS")
  109 + private String generalMergeWriteMS;
  110 +
  111 + @JsonProperty(value = "general.mediaServerId")
  112 + private String generalMediaServerId;
  113 +
  114 + @JsonProperty(value = "general.wait_track_ready_ms")
  115 + private String generalWaitTrackReadyMs;
  116 +
  117 + @JsonProperty(value = "general.wait_add_track_ms")
  118 + private String generalWaitAddTrackMs;
  119 +
  120 + @JsonProperty(value = "general.unready_frame_cache")
  121 + private String generalUnreadyFrameCache;
  122 +
  123 + @JsonProperty(value = "hls.fileBufSize")
  124 + private String hlsFileBufSize;
  125 +
  126 + @JsonProperty(value = "hls.filePath")
  127 + private String hlsFilePath;
  128 +
  129 + @JsonProperty(value = "hls.segDur")
  130 + private String hlsSegDur;
  131 +
  132 + @JsonProperty(value = "hls.segNum")
  133 + private String hlsSegNum;
  134 +
  135 + @JsonProperty(value = "hls.segRetain")
  136 + private String hlsSegRetain;
  137 +
  138 + @JsonProperty(value = "hls.broadcastRecordTs")
  139 + private String hlsBroadcastRecordTs;
  140 +
  141 + @JsonProperty(value = "hls.deleteDelaySec")
  142 + private String hlsDeleteDelaySec;
  143 +
  144 + @JsonProperty(value = "hls.segKeep")
  145 + private String hlsSegKeep;
  146 +
  147 + @JsonProperty(value = "hook.access_file_except_hls")
  148 + private String hookAccessFileExceptHLS;
  149 +
  150 + @JsonProperty(value = "hook.admin_params")
  151 + private String hookAdminParams;
  152 +
  153 + @JsonProperty(value = "hook.alive_interval")
  154 + private Float hookAliveInterval;
  155 +
  156 + @JsonProperty(value = "hook.enable")
  157 + private String hookEnable;
  158 +
  159 + @JsonProperty(value = "hook.on_flow_report")
  160 + private String hookOnFlowReport;
  161 +
  162 + @JsonProperty(value = "hook.on_http_access")
  163 + private String hookOnHttpAccess;
  164 +
  165 + @JsonProperty(value = "hook.on_play")
  166 + private String hookOnPlay;
  167 +
  168 + @JsonProperty(value = "hook.on_publish")
  169 + private String hookOnPublish;
  170 +
  171 + @JsonProperty(value = "hook.on_record_mp4")
  172 + private String hookOnRecordMp4;
  173 +
  174 + @JsonProperty(value = "hook.on_rtsp_auth")
  175 + private String hookOnRtspAuth;
  176 +
  177 + @JsonProperty(value = "hook.on_rtsp_realm")
  178 + private String hookOnRtspRealm;
  179 +
  180 + @JsonProperty(value = "hook.on_shell_login")
  181 + private String hookOnShellLogin;
  182 +
  183 + @JsonProperty(value = "hook.on_stream_changed")
  184 + private String hookOnStreamChanged;
  185 +
  186 + @JsonProperty(value = "hook.on_stream_none_reader")
  187 + private String hookOnStreamNoneReader;
  188 +
  189 + @JsonProperty(value = "hook.on_stream_not_found")
  190 + private String hookOnStreamNotFound;
  191 +
  192 + @JsonProperty(value = "hook.on_server_started")
  193 + private String hookOnServerStarted;
  194 +
  195 + @JsonProperty(value = "hook.on_server_keepalive")
  196 + private String hookOnServerKeepalive;
  197 +
  198 + @JsonProperty(value = "hook.on_send_rtp_stopped")
  199 + private String hookOnSendRtpStopped;
  200 +
  201 + @JsonProperty(value = "hook.on_rtp_server_timeout")
  202 + private String hookOnRtpServerTimeout;
  203 +
  204 + @JsonProperty(value = "hook.timeoutSec")
  205 + private String hookTimeoutSec;
  206 +
  207 + @JsonProperty(value = "http.charSet")
  208 + private String httpCharSet;
  209 +
  210 + @JsonProperty(value = "http.keepAliveSecond")
  211 + private String httpKeepAliveSecond;
  212 +
  213 + @JsonProperty(value = "http.maxReqCount")
  214 + private String httpMaxReqCount;
  215 +
  216 + @JsonProperty(value = "http.maxReqSize")
  217 + private String httpMaxReqSize;
  218 +
  219 + @JsonProperty(value = "http.notFound")
  220 + private String httpNotFound;
  221 +
  222 + @JsonProperty(value = "http.port")
  223 + private int httpPort;
  224 +
  225 + @JsonProperty(value = "http.rootPath")
  226 + private String httpRootPath;
  227 +
  228 + @JsonProperty(value = "http.sendBufSize")
  229 + private String httpSendBufSize;
  230 +
  231 + @JsonProperty(value = "http.sslport")
  232 + private int httpSslPort;
  233 +
  234 + @JsonProperty(value = "multicast.addrMax")
  235 + private String multicastAddrMax;
  236 +
  237 + @JsonProperty(value = "multicast.addrMin")
  238 + private String multicastAddrMin;
  239 +
  240 + @JsonProperty(value = "multicast.udpTTL")
  241 + private String multicastUdpTTL;
  242 +
  243 + @JsonProperty(value = "record.appName")
  244 + private String recordAppName;
  245 +
  246 + @JsonProperty(value = "record.filePath")
  247 + private String recordFilePath;
  248 +
  249 + @JsonProperty(value = "record.fileSecond")
  250 + private String recordFileSecond;
  251 +
  252 + @JsonProperty(value = "record.sampleMS")
  253 + private String recordFileSampleMS;
  254 +
  255 + @JsonProperty(value = "rtmp.handshakeSecond")
  256 + private String rtmpHandshakeSecond;
  257 +
  258 + @JsonProperty(value = "rtmp.keepAliveSecond")
  259 + private String rtmpKeepAliveSecond;
  260 +
  261 + @JsonProperty(value = "rtmp.modifyStamp")
  262 + private String rtmpModifyStamp;
  263 +
  264 + @JsonProperty(value = "rtmp.port")
  265 + private int rtmpPort;
  266 +
  267 + @JsonProperty(value = "rtmp.sslport")
  268 + private int rtmpSslPort;
  269 +
  270 + @JsonProperty(value = "rtp.audioMtuSize")
  271 + private String rtpAudioMtuSize;
  272 +
  273 + @JsonProperty(value = "rtp.clearCount")
  274 + private String rtpClearCount;
  275 +
  276 + @JsonProperty(value = "rtp.cycleMS")
  277 + private String rtpCycleMS;
  278 +
  279 + @JsonProperty(value = "rtp.maxRtpCount")
  280 + private String rtpMaxRtpCount;
  281 +
  282 + @JsonProperty(value = "rtp.videoMtuSize")
  283 + private String rtpVideoMtuSize;
  284 +
  285 + @JsonProperty(value = "rtp_proxy.checkSource")
  286 + private String rtpProxyCheckSource;
  287 +
  288 + @JsonProperty(value = "rtp_proxy.dumpDir")
  289 + private String rtpProxyDumpDir;
  290 +
  291 + @JsonProperty(value = "rtp_proxy.port")
  292 + private int rtpProxyPort;
  293 +
  294 + @JsonProperty(value = "rtp_proxy.port_range")
  295 + private String portRange;
  296 +
  297 + @JsonProperty(value = "rtp_proxy.timeoutSec")
  298 + private String rtpProxyTimeoutSec;
  299 +
  300 + @JsonProperty(value = "rtsp.authBasic")
  301 + private String rtspAuthBasic;
  302 +
  303 + @JsonProperty(value = "rtsp.handshakeSecond")
  304 + private String rtspHandshakeSecond;
  305 +
  306 + @JsonProperty(value = "rtsp.keepAliveSecond")
  307 + private String rtspKeepAliveSecond;
  308 +
  309 + @JsonProperty(value = "rtsp.port")
  310 + private int rtspPort;
  311 +
  312 + @JsonProperty(value = "rtsp.sslport")
  313 + private int rtspSslPort;
  314 +
  315 + @JsonProperty(value = "shell.maxReqSize")
  316 + private String shellMaxReqSize;
  317 +
  318 + @JsonProperty(value = "shell.port")
  319 + private String shellPort;
  320 +
  321 + @JsonProperty(value = "ip")
  322 + private String ip;
  323 +
  324 + private String sdpIp;
  325 +
  326 + private String streamIp;
  327 +
  328 + private String hookIp;
  329 + private String tenantId;
  330 +}
@@ -37,6 +37,175 @@ public interface FastIotConstants { @@ -37,6 +37,175 @@ public interface FastIotConstants {
37 String ORGANIZATION = "organization"; 37 String ORGANIZATION = "organization";
38 String SCENE_REACT = "sceneReact"; 38 String SCENE_REACT = "sceneReact";
39 } 39 }
  40 + interface ModBusRTU{
  41 + /**
  42 + * 原始数据类型
  43 + */
  44 + String ORIGINAL_DATA_TYPE = "originalDataType";
  45 + /**
  46 + * 寄存器地址
  47 + */
  48 + String REGISTER_ADDRESS = "registerAddress";
  49 +
  50 + /**
  51 + * 操作功能码类型
  52 + */
  53 + String OPERATION_TYPE = "operationType";
  54 + /**
  55 + * 缩放因子
  56 + */
  57 + String SCALE_FACTOR = "scaling";
  58 +
  59 + /**
  60 + * 原始hex的标识符
  61 + */
  62 + String SOURCE_DATA_IDENTIFY = "source";
  63 +
  64 + /**
  65 + * 比特位置
  66 + */
  67 + String BIT_MASK = "bitMask";
  68 + }
  69 + interface CacheSipKey {
  70 + String TK_SIP_CACHE_NAME = "tkSipCacheName";
  71 + String TK_OTHER_RECEIVE_RTP_INFO = "TK_OTHER_RECEIVE_RTP_INFO_";
  72 +
  73 + String TK_OTHER_RECEIVE_PS_INFO = "TK_OTHER_RECEIVE_PS_INFO_";
  74 + }
  75 +
  76 + interface MediaServerKey {
  77 +
  78 + String MEDIA_SERVER_CACHE_NAME = "tkMediaServerCacheName";
  79 + String MEDIA_SERVER_PREFIX = "TK_MEDIA_SERVER_";
  80 + String MEDIA_TRANSACTION_USED_PREFIX = "TK_MEDIA_TRANSACTION_";
  81 + String MEDIA_SERVERS_ONLINE_PREFIX = "TK_MEDIA_ONLINE_SERVERS_";
  82 + String MEDIA_STREAM_AUTHORITY = "TK_MEDIA_STREAM_AUTHORITY_";
  83 +
  84 + /** 点播信息的缓存键 */
  85 + String PLAYER_PREFIX = "TK_PLAYER";
  86 +
  87 + /** 回放信息的缓存键 */
  88 + String PLAY_BACK_PREFIX = "TK_PLAY_BACK";
  89 +
  90 + String TK_SERVER_STREAM_PREFIX = "TK_SIGNALLING_STREAM_";
  91 +
  92 + String PLATFORM_SEND_RTP_INFO_PREFIX = "PLATFORM_SEND_RTP_INFO_";
  93 + }
  94 +
  95 + interface ZLMediaKitHttpApi {
  96 + String BASE_URL = "/index/api";
  97 +
  98 + /** 获取流列表,可选筛选参数 */
  99 + String GET_MEDIA_LIST = BASE_URL + "/getMediaList";
  100 +
  101 + /** 获取rtp代理时的某路ssrc rtp信息 */
  102 + String GET_RTP_INFO = BASE_URL + "/getRtpInfo";
  103 +
  104 + /** 获取视频流截图 */
  105 + String GET_STREAM_SNAP = BASE_URL + "/getSnap";
  106 +
  107 + /** 通过fork FFmpeg进程的方式拉流代理,支持任意协议 */
  108 + String ADD_FFMPEG_SOURCE = BASE_URL + "/addFFmpegSource";
  109 +
  110 + /** 关闭ffmpeg拉流代理(流注册成功后,也可以使用close_streams接口替代) */
  111 + String DEL_FFMPEG_SOURCE = BASE_URL + "/delFFmpegSource";
  112 +
  113 + /** 获取服务器配置 */
  114 + String GET_SERVER_CONFIG = BASE_URL + "/getServerConfig";
  115 +
  116 + /** 设置服务器配置 */
  117 + String SET_SERVER_CONFIG = BASE_URL + "/setServerConfig";
  118 +
  119 + /** 创建GB28181 RTP接收端口,如果该端口接收数据超时,则会自动被回收(不用调用closeRtpServer接口) */
  120 + String OPEN_RTP_SERVER = BASE_URL + "/openRtpServer";
  121 +
  122 + /** 关闭GB28181 RTP接收端口 */
  123 + String CLOSE_RTP_SERVER = BASE_URL + "/closeRtpServer";
  124 +
  125 + /**
  126 + * 作为GB28181客户端,启动ps-rtp推流,支持rtp/udp方式;该接口支持rtsp/rtmp等协议转ps-rtp推流。
  127 + * 第一次推流失败会直接返回错误,成功一次后,后续失败也将无限重试。
  128 + */
  129 + String START_SEND_RTP = BASE_URL + "/startSendRtp";
  130 +
  131 + /** 停止GB28181 ps-rtp推流 */
  132 + String STOP_SEND_RTP = BASE_URL + "/stopSendRtp";
  133 +
  134 + /** 重启服务器,只有Daemon方式才能重启,否则是直接关闭! */
  135 + String RESTART_SERVER = BASE_URL + "/restartServer";
  136 + }
  137 +
  138 + interface ZLMediaBody {
  139 + /** 表单数据:异常码 */
  140 + String CODE = "code";
  141 +
  142 + /** 表单数据:数据 */
  143 + String DATA = "data";
  144 +
  145 + /** 表单数据:异常消息 */
  146 + String MSG = "msg";
  147 +
  148 + /** 表单数据:流媒体密钥 */
  149 + String SECRET = "secret";
  150 +
  151 + /** 流媒体服务器信息 */
  152 + String MEDIA = "media";
  153 +
  154 + /** 流媒体服务器信息:ID */
  155 + String MEDIA_ID = "mediaId";
  156 +
  157 + /** 流媒体服务器信息:会话描述协议 */
  158 + String MEDIA_SDP_IP = "sdpIp";
  159 +
  160 + /** 数据流服务ID */
  161 + String SSRCINFO_STREAM = "streamId";
  162 +
  163 + /** 数据流服务的端口 */
  164 + String SSRCINFO_PORT = "streamPort";
  165 +
  166 + /** 同步源标识符 */
  167 + String SSRCINFO_SSRC = "ssrc";
  168 +
  169 + /** 流媒体通道ID */
  170 + String CHANNEL_ID = "channelId";
  171 +
  172 + /** 流媒体通道ID */
  173 + String CALL_ID = "callId";
  174 +
  175 + /** 设备国标编号 */
  176 + String CAMERA_CODE = "cameraCode";
  177 +
  178 + /** 方法类型 */
  179 + String METHOD_TYPE = "methodType";
  180 +
  181 + /** 请求响应事件:头部标签内容 */
  182 + String MSG_HEADER = "msgHeader";
  183 +
  184 + /** 请求响应事件:表单内容 */
  185 + String MSG_CONTEXT = "msgContext";
  186 +
  187 + /** 消息类型,例如:查询、控制等 */
  188 + String MSG_TYPE = "xmlRoot";
  189 +
  190 + /** 订阅处理结果 */
  191 + String SESSION_TYPE = "sessionType";
  192 +
  193 + /** 订阅处理结果 */
  194 + String SSRC_CHECK = "ssrcCheck";
  195 + }
  196 +
  197 + /** 视频流传输模式 */
  198 + interface StreamMode {
  199 + /** tcp 被动模式 */
  200 + String TCP_PASSIVE = "TCP-PASSIVE";
  201 +
  202 + /** tcp 主动模式 */
  203 + String TCP_ACTIVE = "TCP-ACTIVE";
  204 +
  205 + /** udp */
  206 + String UDP = "UDP";
  207 + }
  208 +
40 209
41 interface TBCacheConfig { 210 interface TBCacheConfig {
42 String TB_CACHE_CONFIG_KEY = "TB_CONNECT_CACHE"; 211 String TB_CACHE_CONFIG_KEY = "TB_CONNECT_CACHE";
@@ -66,6 +235,23 @@ public interface FastIotConstants { @@ -66,6 +235,23 @@ public interface FastIotConstants {
66 String NODE = "NODE"; 235 String NODE = "NODE";
67 } 236 }
68 237
  238 + /**
  239 + * 首页统计分析指标
  240 + */
  241 + interface Statistics {
  242 + String TRANSPORT_MSG_COUNT_HOURLY = "transportMsgCountHourly";
  243 + String TRANSPORT_DATAPOINTS_COUNT_HOURLY = "transportDataPointsCountHourly";
  244 + String CREATED_ALARMS_COUNT_HOURLY = "createdAlarmsCountHourly";
  245 + String TRANSPORT_MSG_COUNT = "transportMsgCount";
  246 + String TRANSPORT_DATAPOINTS_COUNT = "transportDataPointsCount";
  247 + String CREATED_ALARMS_COUNT = "createdAlarmsCount";
  248 + }
  249 + /**
  250 + * 单位转换常量
  251 + */
  252 + interface Unit {
  253 + Long ONE_DAY_MILLISECONDS = 86400000L;
  254 + }
69 class DefaultOrder { 255 class DefaultOrder {
70 public static final String CREATE_TIME = "create_time"; 256 public static final String CREATE_TIME = "create_time";
71 } 257 }
@@ -119,6 +305,7 @@ public interface FastIotConstants { @@ -119,6 +305,7 @@ public interface FastIotConstants {
119 class MagicNumber { 305 class MagicNumber {
120 public static final int ZERO = 0; 306 public static final int ZERO = 0;
121 public static final int ONE = 1; 307 public static final int ONE = 1;
  308 + public static final int TWO = 2;
122 public static final int TEN = 10; 309 public static final int TEN = 10;
123 } 310 }
124 311
@@ -219,4 +406,22 @@ public interface FastIotConstants { @@ -219,4 +406,22 @@ public interface FastIotConstants {
219 public static String TK_MSG_EVENT_NODE = "org.thingsboard.rule.engine.yunteng.event.TkMsgEventNode"; 406 public static String TK_MSG_EVENT_NODE = "org.thingsboard.rule.engine.yunteng.event.TkMsgEventNode";
220 } 407 }
221 408
  409 +
  410 + /** 设备扩展信息内容 */
  411 + interface DeviceAdditional {
  412 + /** 摄像头信息 */
  413 + String SIP = "sip";
  414 +
  415 + /** 设备图片 */
  416 + String AVATAR = "avatar";
  417 +
  418 + /** 设备地理位置 */
  419 + String ADDRESS = "address";
  420 +
  421 + /** 地理坐标之经度 */
  422 + String LONGITUDE = "longitude";
  423 +
  424 + /** 地理坐标之维度 */
  425 + String LATITUDE = "latitude";
  426 + }
222 } 427 }
@@ -133,6 +133,15 @@ public final class ModelConstants { @@ -133,6 +133,15 @@ public final class ModelConstants {
133 133
134 /** 产品品类表 */ 134 /** 产品品类表 */
135 public static final String TK_DEVICE_PROFILE_CATEGORY = "tk_device_profile_category"; 135 public static final String TK_DEVICE_PROFILE_CATEGORY = "tk_device_profile_category";
  136 +
  137 + /** 设备接入信息表 */
  138 + public static final String TK_DEVICE_ACCESS_INFORMATION = "tk_device_access_information";
  139 +
  140 + /** ZLMediaKit 流媒体表 */
  141 + public static final String TK_MEDIA_SERVER_NAME = "tk_media_server";
  142 +
  143 + /** 视频通道表 */
  144 + public static final String TK_VIDEO_CHANNEL_NAME = "tk_video_channel";
136 } 145 }
137 146
138 public static class TableFields { 147 public static class TableFields {
  1 +package org.thingsboard.server.common.data.yunteng.constant;
  2 +
  3 +public class SipRequestMethodConstants {
  4 + public static final String ACK = "ACK";
  5 + public static final String BYE = "BYE";
  6 + public static final String CANCEL = "CANCEL";
  7 + public static final String INVITE = "INVITE";
  8 + public static final String OPTIONS = "OPTIONS";
  9 + public static final String REGISTER = "REGISTER";
  10 + public static final String NOTIFY = "NOTIFY";
  11 + public static final String SUBSCRIBE = "SUBSCRIBE";
  12 + public static final String MESSAGE = "MESSAGE";
  13 + public static final String REFER = "REFER";
  14 +
  15 + public static final String INFO = "INFO";
  16 + public static final String PRACK = "PRACK";
  17 +
  18 + public static final String UPDATE = "UPDATE";
  19 +
  20 + public static final String PUBLISH = "PUBLISH";
  21 +}
@@ -120,6 +120,22 @@ public enum ErrorMessage { @@ -120,6 +120,22 @@ public enum ErrorMessage {
120 EZVIZ_API_ERROR(400095,"荧石视频获取TokenAPI调用失败【%s】,错误码【%s】"), 120 EZVIZ_API_ERROR(400095,"荧石视频获取TokenAPI调用失败【%s】,错误码【%s】"),
121 EZVIZ_GET_URL_ERROR(400096,"荧石API调用获取URL失败!!"), 121 EZVIZ_GET_URL_ERROR(400096,"荧石API调用获取URL失败!!"),
122 IMPORT_TCP_ERROR(400097,"TCP产品不能导入INT,DOUBLE,BOOL,TEXT以外的数据类型属性!!"), 122 IMPORT_TCP_ERROR(400097,"TCP产品不能导入INT,DOUBLE,BOOL,TEXT以外的数据类型属性!!"),
  123 + SSRC_INFO_NOT_FOUND(400098,"缓存事务信息未找到,设备【%s】 通道【%s】"),
  124 + VIDEO_CHANNEL_NOT_FOUND(400099,"视频通道不存在"),
  125 + ONLINE_MEDIA_SERVER_NOT_FOUND(400100,"没有可用的流媒体节点"),
  126 + FOUND_VIDEO_DEVICE_FAILED(400101,"视频设备SIP信息丢失"),
  127 + NOT_FOUND_MEDIA_SERVER_FOR_PLAY(400102,"未找到可用于播放的流媒体"),
  128 + RECEIVE_STREAM_FAILED(400103,"开启收流失败"),
  129 + GET_PLAY_PORT_FAILED(400104,"从流媒体【%s】获取点播端口异常"),
  130 + STREAM_INFO_NOT_FOUND_FOR_PLAY(400105,"点播信息未找到"),
  131 + SIP_COMMAND_SEND_FAILED(400106,"sip命令下发失败"),
  132 + NOT_BELONG_CURRENT_CUSTOMER(400107,"该数据不属于当前客户"),
  133 + IMPORT_ERROR(400108,"请使用模板excel重新导入"),
  134 + INTRANET_ERROR(400109,"内网ip+端口不能重复,请重新输入"),
  135 + OUTER_NET_ERROR(400110,"外网ip+端口不能重复,请重新输入"),
  136 +
  137 + DEVICE_NOT_ONLINE(400111,"设备不在线,无法执行相关操作!"),
  138 + GBT_VIDEO_REPETITION(400112,"该设备通道号视频已存在!"),
123 HAVE_NO_PERMISSION(500002,"没有修改权限"), 139 HAVE_NO_PERMISSION(500002,"没有修改权限"),
124 NOT_ALLOED_ISOLATED_IN_MONOLITH(500003,"【monolith】模式下,不能选择【isolated】类型的租户配置"); 140 NOT_ALLOED_ISOLATED_IN_MONOLITH(500003,"【monolith】模式下,不能选择【isolated】类型的租户配置");
125 141
@@ -128,4 +128,10 @@ public class DeviceDTO extends TenantDTO { @@ -128,4 +128,10 @@ public class DeviceDTO extends TenantDTO {
128 128
129 @ApiModelProperty(value = "是否收藏:0否1是") 129 @ApiModelProperty(value = "是否收藏:0否1是")
130 private Integer isCollect; 130 private Integer isCollect;
  131 +
  132 + @ApiModelProperty(value = "OTA升级固件ID")
  133 + private String firmwareId;
  134 +
  135 + @ApiModelProperty(value = "OTA升级软件ID")
  136 + private String softwareId;
131 } 137 }
@@ -88,6 +88,12 @@ public class DeviceProfileDTO extends BaseDTO { @@ -88,6 +88,12 @@ public class DeviceProfileDTO extends BaseDTO {
88 @ApiModelProperty(value = "品类名称") 88 @ApiModelProperty(value = "品类名称")
89 private String categoryName; 89 private String categoryName;
90 90
  91 + @ApiModelProperty(value = "OTA升级固件ID")
  92 + private String firmwareId;
  93 +
  94 + @ApiModelProperty(value = "OTA升级软件ID")
  95 + private String softwareId;
  96 +
91 public DeviceProfileDTO() {} 97 public DeviceProfileDTO() {}
92 98
93 public DeviceProfileDTO( 99 public DeviceProfileDTO(
  1 +package org.thingsboard.server.common.data.yunteng.dto;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.thingsboard.server.common.data.yunteng.common.AddGroup;
  8 +import org.thingsboard.server.common.data.yunteng.enums.TransportTypeEnum;
  9 +
  10 +import javax.validation.constraints.NotEmpty;
  11 +
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@Data
  14 +public class TkDeviceAccessInformationDTO extends TenantDTO {
  15 +
  16 + @ApiModelProperty(value = "内网ip")
  17 + @NotEmpty(
  18 + message = "内网ip不能为空或空字符串",
  19 + groups = {AddGroup.class})
  20 + private String intranetIp;
  21 +
  22 + @ApiModelProperty(value = "内网端口")
  23 + @NotEmpty(
  24 + message = "内网端口不能为空或空字符串",
  25 + groups = {AddGroup.class})
  26 + private String intranetPort;
  27 +
  28 + @ApiModelProperty(value = "外网IP")
  29 + @NotEmpty(
  30 + message = "外网IP不能为空或空字符串",
  31 + groups = {AddGroup.class})
  32 + private String outerNetIp;
  33 +
  34 + @ApiModelProperty(value = "外网端口")
  35 + private String outerNetPort;
  36 +
  37 + @ApiModelProperty(value = "设备接入协议")
  38 + private TransportTypeEnum deviceAgreement;
  39 +
  40 + @ApiModelProperty(value = "sip扩展信息: serverId 服务器id serverRegion 服务器域 password 密码")
  41 + private JsonNode sipExtend;
  42 +
  43 +}
@@ -2,6 +2,7 @@ package org.thingsboard.server.common.data.yunteng.dto; @@ -2,6 +2,7 @@ package org.thingsboard.server.common.data.yunteng.dto;
2 2
3 import io.swagger.annotations.ApiModelProperty; 3 import io.swagger.annotations.ApiModelProperty;
4 import lombok.Data; 4 import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.enums.HexByteOrderEnum;
5 import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType; 6 import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType;
6 import java.io.Serializable; 7 import java.io.Serializable;
7 import java.util.List; 8 import java.util.List;
@@ -28,4 +29,7 @@ public class TkDeviceRpcDTO implements Serializable { @@ -28,4 +29,7 @@ public class TkDeviceRpcDTO implements Serializable {
28 29
29 @ApiModelProperty(value = "寄存器值") 30 @ApiModelProperty(value = "寄存器值")
30 private List<Integer> registerValues; 31 private List<Integer> registerValues;
  32 +
  33 + @ApiModelProperty(value = "顺序")
  34 + private HexByteOrderEnum hexByteOrderEnum;
31 } 35 }
@@ -32,6 +32,7 @@ public class TkThingsModel implements Serializable { @@ -32,6 +32,7 @@ public class TkThingsModel implements Serializable {
32 32
33 private JsonNode functionJson; 33 private JsonNode functionJson;
34 34
  35 + private JsonNode extensionDesc;
35 36
36 private Integer status; 37 private Integer status;
37 38
@@ -26,7 +26,6 @@ public class TkVideoDTO extends TenantDTO { @@ -26,7 +26,6 @@ public class TkVideoDTO extends TenantDTO {
26 private String deviceType; 26 private String deviceType;
27 27
28 @ApiModelProperty(value = "摄像头编号/监控点位编号", required = true) 28 @ApiModelProperty(value = "摄像头编号/监控点位编号", required = true)
29 - @NotEmpty(message = "摄像头编号不能为空或空字符串")  
30 private String sn; 29 private String sn;
31 30
32 @ApiModelProperty(value = "组织ID", required = true) 31 @ApiModelProperty(value = "组织ID", required = true)
@@ -42,7 +41,7 @@ public class TkVideoDTO extends TenantDTO { @@ -42,7 +41,7 @@ public class TkVideoDTO extends TenantDTO {
42 @ApiModelProperty(value = "摄像头描述") 41 @ApiModelProperty(value = "摄像头描述")
43 private String description; 42 private String description;
44 43
45 - @ApiModelProperty(value = "流获取方式:0 手动填写 1平台获取", required = true) 44 + @ApiModelProperty(value = "流获取方式:0 手动填写 1平台获取 2 GBT28281", required = true)
46 private Integer accessMode; 45 private Integer accessMode;
47 46
48 @ApiModelProperty(value = "平台ID") 47 @ApiModelProperty(value = "平台ID")
  1 +package org.thingsboard.server.common.data.yunteng.dto;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +
  7 +import javax.validation.constraints.NotEmpty;
  8 +import java.util.List;
  9 +
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +public class TkVideoGbtDTO extends TenantDTO {
  13 +
  14 + @ApiModelProperty(value = "组织ID", required = true)
  15 + @NotEmpty(message = "组织ID不能为空或空字符串")
  16 + private String organizationId;
  17 +
  18 + @ApiModelProperty(value = "流获取方式:0 手动填写 1平台获取 2 GBT28281", required = true)
  19 + private Integer accessMode;
  20 +
  21 + @ApiModelProperty(value = "多个设备id与通道号")
  22 + private List<TkVideoGbtDeviceDTO> gbtDeviceDTOS;
  23 +
  24 + @ApiModelProperty(value = "是否全部设备 0否 1是")
  25 + private Integer allDevice;
  26 +
  27 +}
  1 +package org.thingsboard.server.common.data.yunteng.dto;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotEmpty;
  7 +import java.util.List;
  8 +
  9 +@Data
  10 +public class TkVideoGbtDeviceDTO {
  11 +
  12 + @ApiModelProperty(value = "设备id", required = true)
  13 + @NotEmpty(message = "设备id不能为空")
  14 + private String deviceID;
  15 +
  16 + @ApiModelProperty(value = "设备名称", required = true)
  17 + @NotEmpty(message = "设备名称不能为空")
  18 + private String deviceName;
  19 +
  20 + @ApiModelProperty(value = "通道号集合")
  21 + @NotEmpty(message = "通道号集合不能为空")
  22 + private List<String> channelNos;
  23 +
  24 +}
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.yunteng.enums.CatalogDataStatusEnum;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Data
  10 +public class CatalogDataDTO {
  11 + /** 命令序列号 */
  12 + private int sn;
  13 +
  14 + private int total;
  15 + private List<VideoChanelDTO> channelList;
  16 + private LocalDateTime lastTime;
  17 + private SipDeviceDTO device;
  18 + private String errorMsg;
  19 + private CatalogDataStatusEnum status;
  20 +}
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import org.springframework.util.ObjectUtils;
  7 +import org.thingsboard.server.common.data.yunteng.config.media.ZLMediaKitServerConfig;
  8 +import org.thingsboard.server.common.data.yunteng.dto.TenantDTO;
  9 +
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@Data
  12 +public class MediaServerDTO extends TenantDTO {
  13 + private static final long serialVersionUID = -2499846994830954839L;
  14 +
  15 + @ApiModelProperty(value = "流媒体的ID,非主键ID")
  16 + private String mediaServerId;
  17 +
  18 + @ApiModelProperty(value = "流媒体的IP")
  19 + private String ip;
  20 +
  21 + @ApiModelProperty(value = "hook使用的IP")
  22 + private String hookIp;
  23 +
  24 + @ApiModelProperty(value = "SDP IP")
  25 + private String sdpIp;
  26 +
  27 + @ApiModelProperty(value = "流IP")
  28 + private String streamIp;
  29 +
  30 + @ApiModelProperty(value = "HTTP端口")
  31 + private Integer httpPort;
  32 +
  33 + @ApiModelProperty(value = "HTTPS端口")
  34 + private Integer httpSslPort;
  35 +
  36 + @ApiModelProperty(value = "RTMP端口")
  37 + private Integer rtmpPort;
  38 +
  39 + @ApiModelProperty(value = "RTMP安全协议端口")
  40 + private Integer rtmpSslPort;
  41 +
  42 + @ApiModelProperty(value = "RTP收流端口(单端口模式有用)")
  43 + private Integer rtpProxyPort;
  44 +
  45 + @ApiModelProperty(value = "RTSP端口")
  46 + private Integer rtspPort;
  47 +
  48 + @ApiModelProperty(value = "RTSP安全协议端口")
  49 + private Integer rtspSslPort;
  50 +
  51 + @ApiModelProperty(value = "是否开启自动配置ZLMediaKit")
  52 + private boolean autoConfig;
  53 +
  54 + @ApiModelProperty(value = "ZLMediaKit鉴权参数")
  55 + private String secret;
  56 +
  57 + @ApiModelProperty(value = "keepalive hook触发间隔,单位秒")
  58 + private Float hookAliveInterval;
  59 +
  60 + @ApiModelProperty(value = "是否使用多端口模式")
  61 + private boolean rtpEnable;
  62 +
  63 + @ApiModelProperty(value = "流媒体服务器的状态")
  64 + private boolean status;
  65 +
  66 + @ApiModelProperty(value = "多端口RTP收流端口范围")
  67 + private String rtpPortRange;
  68 +
  69 + @ApiModelProperty(value = "RTP发流端口范围")
  70 + private String sendRtpPortRange;
  71 +
  72 + @ApiModelProperty(value = "assist服务端口")
  73 + private Integer recordAssistPort;
  74 +
  75 + @ApiModelProperty(value = "最后一次心跳时间")
  76 + private String lastKeepaliveTime;
  77 +
  78 + @ApiModelProperty(value = "是否是默认ZLM")
  79 + private boolean defaultServer;
  80 +
  81 + @ApiModelProperty(value = "当前使用到的端口")
  82 + private Integer currentPort;
  83 +
  84 + public MediaServerDTO(){
  85 +
  86 + }
  87 + public MediaServerDTO(ZLMediaKitServerConfig zlmServerConfig, String sipIp) {
  88 + setId(zlmServerConfig.getGeneralMediaServerId());
  89 + ip = zlmServerConfig.getIp();
  90 + hookIp = ObjectUtils.isEmpty(zlmServerConfig.getHookIp()) ? sipIp : zlmServerConfig.getHookIp();
  91 + sdpIp =
  92 + ObjectUtils.isEmpty(zlmServerConfig.getSdpIp())
  93 + ? zlmServerConfig.getIp()
  94 + : zlmServerConfig.getSdpIp();
  95 + streamIp =
  96 + ObjectUtils.isEmpty(zlmServerConfig.getStreamIp())
  97 + ? zlmServerConfig.getIp()
  98 + : zlmServerConfig.getStreamIp();
  99 + httpPort = zlmServerConfig.getHttpPort();
  100 + httpSslPort = zlmServerConfig.getHttpSslPort();
  101 + rtmpPort = zlmServerConfig.getRtmpPort();
  102 + rtmpSslPort = zlmServerConfig.getRtmpSslPort();
  103 + rtpProxyPort = zlmServerConfig.getRtpProxyPort();
  104 + rtspPort = zlmServerConfig.getRtspPort();
  105 + rtspSslPort = zlmServerConfig.getRtspSslPort();
  106 + autoConfig = true; // 默认值true;
  107 + secret = zlmServerConfig.getApiSecret();
  108 + hookAliveInterval = zlmServerConfig.getHookAliveInterval();
  109 + rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
  110 + rtpPortRange = zlmServerConfig.getPortRange().replace("_", ","); // 默认使用30000,30500作为级联时发送流的端口号
  111 + recordAssistPort = 0; // 默认关闭
  112 + }
  113 +}
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.enums.PTZCommandEnum;
  6 +
  7 +import java.io.Serializable;
  8 +
  9 +@Data
  10 +public class PTZCmdDTO implements Serializable {
  11 +
  12 + @ApiModelProperty(value = "控制指令")
  13 + private PTZCommandEnum command;
  14 +
  15 + @ApiModelProperty(value = "水平速度")
  16 + private int horizonSpeed;
  17 +
  18 + @ApiModelProperty(value = "垂直速度")
  19 + private int verticalSpeed;
  20 +
  21 + @ApiModelProperty(value = "缩放速度")
  22 + private int zoomSpeed;
  23 +}
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.enums.InviteStreamTypeEnum;
  6 +
  7 +@Data
  8 +public class SendRtpItemDTO {
  9 + @ApiModelProperty(value = "推流ip")
  10 + private String ip;
  11 +
  12 + @ApiModelProperty(value = "推流端口")
  13 + private int port;
  14 +
  15 + @ApiModelProperty(value = "推流标识")
  16 + private String ssrc;
  17 +
  18 + @ApiModelProperty(value = "平台id")
  19 + private String platformId;
  20 +
  21 + @ApiModelProperty(value = "对应设备id")
  22 + private String deviceId;
  23 +
  24 + @ApiModelProperty(value = "直播流的应用名")
  25 + private String app;
  26 +
  27 + @ApiModelProperty(value = "通道id")
  28 + private String channelId;
  29 +
  30 + @ApiModelProperty(value = "推流状态:0 等待设备推流上来 1 等待上级平台回复ack 2 推流中")
  31 + private int status = 0;
  32 +
  33 + @ApiModelProperty(value = "设备推流的streamId")
  34 + private String streamId;
  35 +
  36 + @ApiModelProperty(value = "是否为tcp")
  37 + private boolean tcp;
  38 +
  39 + @ApiModelProperty(value = "是否为tcp主动模式")
  40 + private boolean tcpActive;
  41 +
  42 + @ApiModelProperty(value = "自己推流使用的端口")
  43 + private int localPort;
  44 +
  45 + @ApiModelProperty(value = "使用的流媒体")
  46 + private String mediaServerId;
  47 +
  48 + @ApiModelProperty(value = "使用的服务的ID")
  49 + private String serverId;
  50 +
  51 + @ApiModelProperty(value = "invite 的 callId")
  52 + private String CallId;
  53 +
  54 + @ApiModelProperty(value = "invite 的 fromTag")
  55 + private String fromTag;
  56 +
  57 + @ApiModelProperty(value = "invite 的 toTag")
  58 + private String toTag;
  59 +
  60 + @ApiModelProperty(value = "发送时,rtp的pt(uint8_t),不传时默认为96")
  61 + private int pt = 96;
  62 +
  63 + @ApiModelProperty(value = "发送时,rtp的负载类型。为true时,负载为ps;为false时,为es")
  64 + private boolean usePs = true;
  65 +
  66 + @ApiModelProperty(value = "当usePs 为false时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0")
  67 + private boolean onlyAudio = false;
  68 +
  69 + @ApiModelProperty(value = "是否开启rtcp保活")
  70 + private boolean rtcp = false;
  71 +
  72 + @ApiModelProperty(value = "播放类型")
  73 + private InviteStreamTypeEnum playType;
  74 +}
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  6 +
  7 +import java.io.Serializable;
  8 +
  9 +@Data
  10 +public class SipDeviceDTO implements Serializable {
  11 + @ApiModelProperty(value = "设备国标编号")
  12 + private String cameraCode;
  13 +
  14 + @ApiModelProperty(value = "wan地址")
  15 + private String hostAddress;
  16 +
  17 + @ApiModelProperty(value = "设备访问平台的IP")
  18 + private String localIp;
  19 +
  20 + @ApiModelProperty(value = "传输协议:UDP/TCP")
  21 + private String transport;
  22 +
  23 + @ApiModelProperty(value = "字符集")
  24 + private String charset;
  25 +
  26 + @ApiModelProperty(value = "收流IP")
  27 + private String sdpIp;
  28 +
  29 + @ApiModelProperty(value = "数据流传输模式:UDP / TCP-ACTIVE:tcp主动模式 / TCP-PASSIVE:tcp被动模式")
  30 + private String streamMode;
  31 +
  32 + @ApiModelProperty(value = "通道个数")
  33 + private Integer channelCount;
  34 +
  35 + @ApiModelProperty(value = "设备使用的媒体id, 默认为null")
  36 + private String mediaServerId;
  37 +
  38 + @ApiModelProperty(value = "是否开启ssrc校验,默认关闭,开启可以防止串流")
  39 + private boolean ssrcCheck = true;
  40 + @ApiModelProperty(value = "生产厂商")
  41 + private String manufacturer;
  42 +
  43 + @ApiModelProperty(value = "设备名称")
  44 + private String name;
  45 +
  46 + @ApiModelProperty(value = "设备型号")
  47 + private String model;
  48 +
  49 + @ApiModelProperty(value = "固件版本")
  50 + private String firmware;
  51 + public Integer getStreamModeForParam() {
  52 + if (FastIotConstants.StreamMode.UDP.equalsIgnoreCase(streamMode)) {
  53 + return 0;
  54 + }else if (FastIotConstants.StreamMode.TCP_PASSIVE.equalsIgnoreCase(streamMode)) {
  55 + return 1;
  56 + }else if (FastIotConstants.StreamMode.TCP_ACTIVE.equalsIgnoreCase(streamMode)) {
  57 + return 2;
  58 + }
  59 + return 0;
  60 + }
  61 +}
  1 +package org.thingsboard.server.common.data.yunteng.dto.sip;
  2 +
  3 +import lombok.Builder;
  4 +import lombok.Data;
  5 +
  6 +import java.io.Serializable;
  7 +
  8 +@Data
  9 +@Builder
  10 +public class SipMessageHeaderDTO implements Serializable {
  11 + private String callId;
  12 + private String viaTag;
  13 + private String fromTag;
  14 + private String toTag;
  15 + private String viaBranch;
  16 +}