panlinlin
2021-02-25 0f58218badea86a5209ae7f1ccd60b7cb4b26eee
Merge remote-tracking branch 'origin/master' into wvp-28181-2.0

# Conflicts:
# README.md
# src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java
# src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEvent.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
# src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
# src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
# src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
# src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
# src/main/java/com/genersoft/iot/vmp/vmanager/SseController/SseController.java
# src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java
# src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
# src/main/resources/wvp.sqlite
# web_src/.postcssrc.js
# web_src/src/components/UiHeader.vue
# web_src/src/main.js
# web_src/src/router/index.js
54个文件已修改
13个文件已添加
1 文件已重命名
4072 ■■■■ 已修改文件
README.md 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/UserSetup.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/BaiduPoint.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/MobilePosition.java 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/WvpSipDate.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEvent.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEvent.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 516 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java 515 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/NotifyRequestProcessor.java 384 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/OtherRequestProcessor.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/NumericUtil.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMobilePositionMapper.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/GpsUtil.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/MobilePosition/MobilePositionController.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/SseController/SseController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfig.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControl.java 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceQuery.java 87 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/web/AuthController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/.postcssrc.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/index.html 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/package-lock.json 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/GeoConvertTools.js 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/Login.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/UiHeader.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/devicePosition.vue 388 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/videoList.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/main.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/router/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -13,6 +13,12 @@
# gitee同步仓库
https://gitee.com/18010473990/wvp-GB28181.git
# 截图
![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_1.png)
![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_2.png)
![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_20201012_151459.png)
![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_20201012_152643.png)
![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_20201012_151606.png)
# 1.0 支持特性  
1. 视频预览;  
@@ -36,8 +42,32 @@
19. 支持播放h265, g.711格式的流(需要将closeWaitRTPInfo设为false)
20. 报警信息处理,支持向前端推送报警信息
# 2.0 支持特性
- [ ] 国标通道向上级联
# 新支持特性
1. 集成web界面, 不需要单独部署前端服务, 直接利用wvp内置文件服务部署, 随wvp一起部署;
2. 支持平台接入, 针对大平台大量设备的情况进行优化;
3. 支持检索,通道筛选;
4. 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;
5. 支持启用udp多端口模式, 提高udp模式下媒体传输性能;
6. 支持通道是否含有音频的设置;
7. 支持通道子目录查询;
8. 支持udp/tcp国标流传输模式;
9. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址
10. 支持国标网络校时
11. 支持公网部署, 支持wvp与zlm分开部署
12. 支持播放h265, g.711格式的流
13. 支持固定流地址和自动点播,同时支持未点播时直接播放流地址,代码自动发起点播.  ( [查看WIKI](https://github.com/648540858/wvp-GB28181-pro/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E5%9B%BA%E5%AE%9A%E6%92%AD%E6%94%BE%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%87%AA%E5%8A%A8%E7%82%B9%E6%92%AD))
14. 报警信息处理,支持向前端推送报警信息
15. 支持订阅与通知方法
   -  [X] 移动位置订阅
   -  [X] 移动位置通知处理
   -  [ ] 报警事件订阅
   -  [X] 报警事件通知处理
   -  [ ] 设备目录订阅
   -  [X] 设备目录通知处理
16. 移动位置查询和显示,可通过配置文件设置移动位置历史是否存储
# 2.0 支持特性
- [ ] 国标通道向上级联
    - [X] WEB添加上级平台
    - [X] 注册
    - [X] 心跳保活
@@ -45,13 +75,18 @@
    - [X] 通道推送
    - [ ] 点播
    - [ ] 云台控制
- [ ] 添加RTSP视频
- [ ] 添加ONVIF探测局域网内的设备
- [ ] 添加RTMP视频
- [ ] 添加系统配置
- [ ] 添加用户管理
- [ ] 添加RTSP视频
- [ ] 添加ONVIF探测局域网内的设备
- [ ] 添加RTMP视频
- [ ] 添加系统配置
- [ ] 添加用户管理
# 待实现:
上级级联
推流列表
拉流列表
web界面系统设置
使用mysql作为数据库
# 项目部署
参考:[WIKI](https://github.com/648540858/wvp-GB28181-pro/wiki)
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
@@ -3,7 +3,6 @@
import java.util.logging.LogManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -1,7 +1,7 @@
package com.genersoft.iot.vmp.common;
/**    
 * @Description:TODO(这里用一句话描述这个类的作用)
 * @Description: 定义常量
 * @author: swwheihei
 * @date:   2019年5月30日 下午3:04:04   
 *   
src/main/java/com/genersoft/iot/vmp/conf/UserSetup.java
New file
@@ -0,0 +1,18 @@
package com.genersoft.iot.vmp.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration("userSetup")
public class UserSetup {
    @Value("${userSettings.savePositionHistory}")
    boolean savePositionHistory;
    public boolean getSavePositionHistory() {
        return savePositionHistory;
    }
    public void setSavePositionHistory(boolean savePositionHistory) {
        this.savePositionHistory = savePositionHistory;
    }
}
src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java
@@ -4,7 +4,7 @@
import org.springframework.context.annotation.Configuration;
/**    
 * @Description:TODO(这里用一句话描述这个类的作用)
 * @Description: 获取数据库配置
 * @author: swwheihei
 * @date:   2020年5月6日 下午2:46:00     
 */
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -149,8 +149,6 @@
                    }
                }
            }
        // } else if (status == Response.TRYING) {
            // trying不会回复
        } else if ((status >= 100) && (status < 200)) {
            // 增加其它无需回复的响应,如101、180等
        } else {
src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java
@@ -91,8 +91,10 @@
        long time = date.getTime();
        Random rand = new Random();
        long pad = rand.nextLong();
        String nonceString = (new Long(time)).toString()
                + (new Long(pad)).toString();
        // String nonceString = (new Long(time)).toString()
        //         + (new Long(pad)).toString();
        String nonceString = Long.valueOf(time).toString()
                + Long.valueOf(pad).toString();
        byte mdbytes[] = messageDigest.digest(nonceString.getBytes());
        // Convert the mdbytes array into a hex string.
        return toHexString(mdbytes);
@@ -191,7 +193,7 @@
        // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。
        // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
        String cNonce = authHeader.getCNonce();
        //String cNonce = authHeader.getCNonce();
        // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
        int nc = authHeader.getNonceCount();
src/main/java/com/genersoft/iot/vmp/gb28181/bean/BaiduPoint.java
New file
@@ -0,0 +1,24 @@
package com.genersoft.iot.vmp.gb28181.bean;
public class BaiduPoint {
    String bdLng;
    String bdLat;
    public String getBdLng() {
        return bdLng;
    }
    public void setBdLng(String bdLng) {
        this.bdLng = bdLng;
    }
    public String getBdLat() {
        return bdLat;
    }
    public void setBdLat(String bdLat) {
        this.bdLat = bdLat;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/MobilePosition.java
New file
@@ -0,0 +1,166 @@
package com.genersoft.iot.vmp.gb28181.bean;
/**
 * @Description: 移动位置bean
 * @author: lawrencehj
 * @date: 2021年1月23日
 */
public class MobilePosition {
    /**
     * 设备Id
     */
    private String deviceId;
    /**
     * 设备名称
     */
    private String deviceName;
    /**
     * 通知时间
     */
    private String time;
    /**
     * 经度
     */
    private double longitude;
    /**
     * 纬度
     */
    private double latitude;
    /**
     * 海拔高度
     */
    private double altitude;
    /**
     * 速度
     */
    private double speed;
    /**
     * 方向
     */
    private double direction;
    /**
     * 位置信息上报来源(Mobile Position、GPS Alarm)
     */
    private String reportSource;
    /**
     * 国内地理坐标系(GCJ-02 / BD-09)
     */
    private String GeodeticSystem;
    /**
     * 国内坐标系:经度坐标
     */
    private String cnLng;
    /**
     * 国内坐标系:纬度坐标
     */
    private String cnLat;
    public String getDeviceId() {
        return deviceId;
    }
    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }
    public String getDeviceName() {
        return deviceName;
    }
    public void setDeviceName(String deviceName) {
        this.deviceName = deviceName;
    }
    public String getTime() {
        return time;
    }
    public void setTime(String time) {
        this.time = time;
    }
    public double getLongitude() {
        return longitude;
    }
    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }
    public double getLatitude() {
        return latitude;
    }
    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }
    public double getAltitude() {
        return altitude;
    }
    public void setAltitude(double altitude) {
        this.altitude = altitude;
    }
    public double getSpeed() {
        return speed;
    }
    public void setSpeed(double speed) {
        this.speed = speed;
    }
    public double getDirection() {
        return direction;
    }
    public void setDirection(double direction) {
        this.direction = direction;
    }
    public String getReportSource() {
        return reportSource;
    }
    public void setReportSource(String reportSource) {
        this.reportSource = reportSource;
    }
    public String getGeodeticSystem() {
        return GeodeticSystem;
    }
    public void setGeodeticSystem(String geodeticSystem) {
        GeodeticSystem = geodeticSystem;
    }
    public String getCnLng() {
        return cnLng;
    }
    public void setCnLng(String cnLng) {
        this.cnLng = cnLng;
    }
    public String getCnLat() {
        return cnLat;
    }
    public void setCnLat(String cnLat) {
        this.cnLat = cnLat;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
@@ -1,7 +1,7 @@
package com.genersoft.iot.vmp.gb28181.bean;
import gov.nist.javax.sip.header.SIPDate;
//import gov.nist.javax.sip.header.SIPDate;
import java.util.List;
src/main/java/com/genersoft/iot/vmp/gb28181/bean/WvpSipDate.java
@@ -1,14 +1,100 @@
package com.genersoft.iot.vmp.gb28181.bean;
import gov.nist.core.InternalErrorHandler;
import gov.nist.javax.sip.header.SIPDate;
import java.util.*;
/**
 * 重写jain sip的SIPDate解决与国标时间格式不一致的问题
 */
public class WvpSipDate extends SIPDate {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private Calendar javaCal;
    public WvpSipDate(long timeMillis) {
        super(timeMillis);
        this.javaCal = new GregorianCalendar(TimeZone.getDefault(), Locale.getDefault());
        Date date = new Date(timeMillis);
        this.javaCal.setTime(date);
        this.wkday = this.javaCal.get(7);
        switch(this.wkday) {
            case 1:
                this.sipWkDay = "Sun";
                break;
            case 2:
                this.sipWkDay = "Mon";
                break;
            case 3:
                this.sipWkDay = "Tue";
                break;
            case 4:
                this.sipWkDay = "Wed";
                break;
            case 5:
                this.sipWkDay = "Thu";
                break;
            case 6:
                this.sipWkDay = "Fri";
                break;
            case 7:
                this.sipWkDay = "Sat";
                break;
            default:
                InternalErrorHandler.handleException("No date map for wkday " + this.wkday);
        }
        this.day = this.javaCal.get(5);
        this.month = this.javaCal.get(2);
        switch(this.month) {
            case 0:
                this.sipMonth = "Jan";
                break;
            case 1:
                this.sipMonth = "Feb";
                break;
            case 2:
                this.sipMonth = "Mar";
                break;
            case 3:
                this.sipMonth = "Apr";
                break;
            case 4:
                this.sipMonth = "May";
                break;
            case 5:
                this.sipMonth = "Jun";
                break;
            case 6:
                this.sipMonth = "Jul";
                break;
            case 7:
                this.sipMonth = "Aug";
                break;
            case 8:
                this.sipMonth = "Sep";
                break;
            case 9:
                this.sipMonth = "Oct";
                break;
            case 10:
                this.sipMonth = "Nov";
                break;
            case 11:
                this.sipMonth = "Dec";
                break;
            default:
                InternalErrorHandler.handleException("No date map for month " + this.month);
        }
        this.year = this.javaCal.get(1);
        this.hour = this.javaCal.get(11);
        this.minute = this.javaCal.get(12);
        this.second = this.javaCal.get(13);
    }
    @Override
@@ -48,7 +134,7 @@
            var6 = "" + this.second;
        }
        int var8 = this.getJavaCal().get(14);
        int var8 = this.javaCal.get(14);
        String var7;
        if (var8 < 10) {
            var7 = "00" + var8;
src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java
@@ -65,4 +65,5 @@
        alarmEvent.setAlarmInfo(deviceAlarm);
        applicationEventPublisher.publishEvent(alarmEvent);
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
@@ -1,21 +1,13 @@
package com.genersoft.iot.vmp.gb28181.event;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.sip.ResponseEvent;
import javax.sip.message.Request;
import java.util.EventObject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class SipSubscribe {
    private final static Logger logger = LoggerFactory.getLogger(SipSubscribe.class);
    private Map<String, SipSubscribe.Event> errorSubscribes = new ConcurrentHashMap<>();
src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEvent.java
@@ -10,6 +10,11 @@
 */
public class AlarmEvent extends ApplicationEvent {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    public AlarmEvent(Object source) {
        super(source);
    }
src/main/java/com/genersoft/iot/vmp/gb28181/event/offline/OfflineEvent.java
@@ -3,18 +3,17 @@
import org.springframework.context.ApplicationEvent;
/**    
 * @Description:TODO(这里用一句话描述这个类的作用)
 * @Description: 离线事件类
 * @author: swwheihei
 * @date:   2020年5月6日 上午11:33:13     
 */
public class OfflineEvent extends ApplicationEvent {
    
    /**
     * @Title:  OutlineEvent
     * @Description:    TODO(这里用一句话描述这个方法的作用)
     * @param:  @param source
     * @throws
     */
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    public OfflineEvent(Object source) {
        super(source);
    }
src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java
@@ -3,18 +3,17 @@
import org.springframework.context.ApplicationEvent;
/**    
 * @Description:TODO(这里用一句话描述这个类的作用)
 * @Description: 在线事件类
 * @author: swwheihei
 * @date:   2020年5月6日 上午11:32:56     
 */
public class OnlineEvent extends ApplicationEvent {
    /**
     * @Title:  OnlineEvent
     * @Description:    TODO(这里用一句话描述这个方法的作用)
     * @param:  @param source
     * @throws
     */
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    public OnlineEvent(Object source) {
        super(source);
    }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
@@ -19,6 +19,8 @@
import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
@@ -36,6 +38,7 @@
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.CancelRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.InviteRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.MessageRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.NotifyRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.OtherRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.SubscribeRequestProcessor;
@@ -49,7 +52,7 @@
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**    
 * @Description:TODO(这里用一句话描述这个类的作用)
 * @Description: SIP信令处理分配
 * @author: swwheihei
 * @date:   2020年5月3日 下午4:24:37     
 */
@@ -78,7 +81,7 @@
    @Autowired
    private SIPCommanderFroPlatform cmderFroPlatform;
    @Autowired
    private RedisUtil redis;
    
@@ -122,6 +125,7 @@
        Request request = evt.getRequest();
        String method = request.getMethod();
//        logger.info("接收到消息:"+request.getMethod());
//        sipSubscribe.getSubscribe(evt.getServerTransaction().getBranchId()).response(evt);
        if (Request.INVITE.equals(method)) {
            InviteRequestProcessor processor = new InviteRequestProcessor();
            processor.setRequestEvent(evt);
@@ -161,7 +165,6 @@
            processor.setRequestEvent(evt);
            return processor;
        } else if (Request.MESSAGE.equals(method)) {
            MessageRequestProcessor processor = new MessageRequestProcessor();
            processor.setRequestEvent(evt);
            processor.setTcpSipProvider(getTcpSipProvider());
@@ -175,8 +178,23 @@
            processor.setStorager(storager);
            processor.setRedisCatchStorage(redisCatchStorage);
            return processor;
        } else if (Request.NOTIFY.equalsIgnoreCase(method)) {
            NotifyRequestProcessor processor = new NotifyRequestProcessor();
            processor.setRequestEvent(evt);
            processor.setTcpSipProvider(getTcpSipProvider());
            processor.setUdpSipProvider(getUdpSipProvider());
            processor.setPublisher(publisher);
            processor.setRedis(redis);
            processor.setDeferredResultHolder(deferredResultHolder);
            processor.setOffLineDetector(offLineDetector);
            processor.setCmder(cmder);
            processor.setStorager(storager);
            processor.setRedisCatchStorage(redisCatchStorage);
            return processor;
        } else {
            return new OtherRequestProcessor();
            OtherRequestProcessor processor = new OtherRequestProcessor();
            processor.setRequestEvent(evt);
            return processor;
        }
    }
    
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/CheckForAllRecordsThread.java
New file
@@ -0,0 +1,78 @@
package com.genersoft.iot.vmp.gb28181.transmit.callback;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import com.genersoft.iot.vmp.gb28181.transmit.request.impl.MessageRequestProcessor;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import org.slf4j.Logger;
@SuppressWarnings("unchecked")
public class CheckForAllRecordsThread extends Thread {
    private String key;
    private RecordInfo recordInfo;
    private RedisUtil redis;
    private Logger logger;
    private DeferredResultHolder deferredResultHolder;
    public CheckForAllRecordsThread(String key, RecordInfo recordInfo) {
        this.key = key;
        this.recordInfo = recordInfo;
    }
    public void run() {
        String cacheKey = this.key;
        for (long stop = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); stop > System.nanoTime();) {
            List<Object> cacheKeys = redis.scan(cacheKey + "_*");
            List<RecordItem> totalRecordList = new ArrayList<RecordItem>();
            for (int i = 0; i < cacheKeys.size(); i++) {
                totalRecordList.addAll((List<RecordItem>) redis.get(cacheKeys.get(i).toString()));
            }
            if (totalRecordList.size() < this.recordInfo.getSumNum()) {
                logger.info("已获取" + totalRecordList.size() + "项录像数据,共" + this.recordInfo.getSumNum() + "项");
            } else {
                logger.info("录像数据已全部获取,共" + this.recordInfo.getSumNum() + "项");
                this.recordInfo.setRecordList(totalRecordList);
                for (int i = 0; i < cacheKeys.size(); i++) {
                    redis.del(cacheKeys.get(i).toString());
                }
                break;
            }
        }
        // 自然顺序排序, 元素进行升序排列
        this.recordInfo.getRecordList().sort(Comparator.naturalOrder());
        RequestMessage msg = new RequestMessage();
        String deviceId = recordInfo.getDeviceId();
        msg.setDeviceId(deviceId);
        msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
        msg.setData(recordInfo);
        deferredResultHolder.invokeResult(msg);
        logger.info("处理完成,返回结果");
        MessageRequestProcessor.threadNameList.remove(cacheKey);
    }
    public void setRedis(RedisUtil redis) {
        this.redis = redis;
    }
    public void setDeferredResultHolder(DeferredResultHolder deferredResultHolder) {
        this.deferredResultHolder = deferredResultHolder;
    }
    public void setLogger(Logger logger) {
        this.logger = logger;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
@@ -1,6 +1,5 @@
package com.genersoft.iot.vmp.gb28181.transmit.callback;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -10,14 +9,23 @@
import org.springframework.web.context.request.async.DeferredResult;
/**    
 * @Description:TODO(这里用一句话描述这个类的作用)
 * @Description: 异步请求处理
 * @author: swwheihei
 * @date:   2020年5月8日 下午7:59:05     
 */
@SuppressWarnings(value = {"rawtypes", "unchecked"})
@Component
public class DeferredResultHolder {
    
    public static final String CALLBACK_CMD_DEVICESTATUS = "CALLBACK_DEVICESTATUS";
    public static final String CALLBACK_CMD_DEVICEINFO = "CALLBACK_DEVICEINFO";
    public static final String CALLBACK_CMD_DEVICECONTROL = "CALLBACK_DEVICECONTROL";
    public static final String CALLBACK_CMD_DEVICECONFIG = "CALLBACK_DEVICECONFIG";
    public static final String CALLBACK_CMD_CONFIGDOWNLOAD = "CALLBACK_CONFIGDOWNLOAD";
    
    public static final String CALLBACK_CMD_CATALOG = "CALLBACK_CATALOG";
    
@@ -27,6 +35,12 @@
    public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
    public static final String CALLBACK_CMD_MOBILEPOSITION = "CALLBACK_MOBILEPOSITION";
    public static final String CALLBACK_CMD_PRESETQUERY = "CALLBACK_PRESETQUERY";
    public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
    private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
    public void put(String key, DeferredResult result) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java
@@ -1,7 +1,7 @@
package com.genersoft.iot.vmp.gb28181.transmit.callback;
/**    
 * @Description:TODO(这里用一句话描述这个类的作用)
 * @Description: 请求信息定义
 * @author: swwheihei
 * @date:   2020年5月8日 下午1:09:18     
 */
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -1,6 +1,5 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
@@ -115,24 +114,35 @@
    /**
     * 音视频录像控制
     * 
     * @param device  视频设备
     * @param channelId  预览通道
     * @param device          视频设备
     * @param channelId      预览通道
     * @param recordCmdStr    录像命令:Record / StopRecord
     */
    boolean recordCmd(Device device,String channelId);
    boolean recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent);
    
    /**
     * 远程启动控制命令
     *
     * @param device    视频设备
     */
    boolean teleBootCmd(Device device);
    /**
     * 报警布防/撤防命令
     * 
     * @param device  视频设备
     * @param device      视频设备
     * @param setGuard    true: SetGuard, false: ResetGuard
     */
    boolean guardCmd(Device device);
    boolean guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent);
    
    /**
     * 报警复位命令
     * 
     * @param device  视频设备
     * @param device        视频设备
     * @param alarmMethod    报警方式(可选)
     * @param alarmType        报警类型(可选)
     */
    boolean alarmCmd(Device device);
    boolean alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent);
    
    /**
     * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
@@ -140,14 +150,17 @@
     * @param device  视频设备
     * @param channelId  预览通道
     */
    boolean iFameCmd(Device device,String channelId);
    boolean iFrameCmd(Device device, String channelId);
    
    /**
     * 看守位控制命令
     * 
     * @param device  视频设备
     * @param device        视频设备
     * @param enabled        看守位使能:1 = 开启,0 = 关闭
     * @param resetTime        自动归位时间间隔,开启看守位时使用,单位:秒(s)
     * @param presetIndex    调用预置位编号,开启看守位时使用,取值范围0~255
     */
    boolean homePositionCmd(Device device);
    boolean homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent);
    
    /**
     * 设备配置命令
@@ -156,13 +169,24 @@
     */
    boolean deviceConfigCmd(Device device);
    
        /**
     * 设备配置命令:basicParam
     *
     * @param device              视频设备
     * @param channelId            通道编码(可选)
     * @param name                设备/通道名称(可选)
     * @param expiration        注册过期时间(可选)
     * @param heartBeatInterval    心跳间隔时间(可选)
     * @param heartBeatCount    心跳超时次数(可选)
     */
    boolean deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent);
    
    /**
     * 查询设备状态
     * 
     * @param device 视频设备
     */
    boolean deviceStatusQuery(Device device);
    boolean deviceStatusQuery(Device device, SipSubscribe.Event errorEvent);
    
    /**
     * 查询设备信息
@@ -191,30 +215,65 @@
    /**
     * 查询报警信息
     * 
     * @param device 视频设备
     * @param device        视频设备
     * @param startPriority    报警起始级别(可选)
     * @param endPriority    报警终止级别(可选)
     * @param alarmMethod    报警方式条件(可选)
     * @param alarmType        报警类型
     * @param startTime        报警发生起始时间(可选)
     * @param endTime        报警发生终止时间(可选)
     * @return                true = 命令发送成功
     */
    boolean alarmInfoQuery(Device device);
    boolean alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod,
                            String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent);
    
    /**
     * 查询设备配置
     * 
     * @param device 视频设备
     * @param device         视频设备
     * @param channelId        通道编码(可选)
     * @param configType    配置类型:
     */
    boolean configQuery(Device device);
    boolean deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent);
    
    /**
     * 查询设备预置位置
     * 
     * @param device 视频设备
     */
    boolean presetQuery(Device device);
    boolean presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent);
    
    /**
     * 查询移动设备位置数据
     * 
     * @param device 视频设备
     */
    boolean mobilePostitionQuery(Device device);
    boolean mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent);
    /**
     * 订阅、取消订阅移动位置
     *
     * @param device    视频设备
     * @param expires    订阅超时时间(值=0时为取消订阅)
     * @param interval    上报时间间隔
     * @return            true = 命令发送成功
     */
    boolean mobilePositionSubscribe(Device device, int expires, int interval);
    /**
     * 订阅、取消订阅报警信息
     * @param device        视频设备
     * @param expires        订阅过期时间(0 = 取消订阅)
     * @param startPriority    报警起始级别(可选)
     * @param endPriority    报警终止级别(可选)
     * @param alarmMethods    报警方式条件(可选)
     * @param alarmType        报警类型
     * @param startTime        报警发生起始时间(可选)
     * @param endTime        报警发生终止时间(可选)
     * @return                true = 命令发送成功
     */
    boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime);
    /**
     * 释放rtpserver
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
@@ -168,4 +168,51 @@
        request.setContent(content, contentTypeHeader);
        return request;
    }
    public Request createSubscribeRequest(Device device, String content, String viaTag, String fromTag, String toTag, Integer expires, String event) throws ParseException, InvalidArgumentException, PeerUnavailableException {
        Request request = null;
        // sipuri
        SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
        // via
        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
                device.getTransport(), viaTag);
        viaHeader.setRPort();
        viaHeaders.add(viaHeader);
        // from
        SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(),
                sipConfig.getSipIp() + ":" + sipConfig.getSipPort());
        Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
        FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag);
        // to
        SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), sipConfig.getSipDomain());
        Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
        ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, toTag);
        // callid
        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
                : udpSipProvider.getNewCallId();
        // Forwards
        MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
        // ceq
        CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(1L, Request.SUBSCRIBE);
        request = sipFactory.createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
                toHeader, viaHeaders, maxForwards);
        Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(), sipConfig.getSipIp()+":"+sipConfig.getSipPort()));
        request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
        // Expires
        ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(expires);
        request.addHeader(expireHeader);
        // Event
        EventHeader eventHeader = sipFactory.createHeaderFactory().createEventHeader(event);
        request.addHeader(eventHeader);
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "MANSCDP+xml");
        request.setContent(content, contentTypeHeader);
        return request;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -1,21 +1,18 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import java.text.ParseException;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sip.*;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.header.Header;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
@@ -34,6 +31,8 @@
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
/**    
 * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -235,7 +234,8 @@
            ptzXml.append("</Info>\r\n");
            ptzXml.append("</Control>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", null);
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtz" + tm, null);
            
            transmitRequest(device, request);
            return true;
@@ -271,7 +271,8 @@
            ptzXml.append("</Info>\r\n");
            ptzXml.append("</Control>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", null);
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtz" + tm, null);
            transmitRequest(device, request);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
@@ -383,7 +384,8 @@
            content.append("y="+ssrc+"\r\n");//ssrc
            Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null, ssrc);
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc);
            ClientTransaction transaction = transmitRequest(device, request, errorEvent);
            streamSession.put(streamId, transaction);
@@ -482,7 +484,8 @@
            content.append("y="+ssrc+"\r\n");//ssrc
            
            Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "playback", null);
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null);
            ClientTransaction transaction = transmitRequest(device, request, errorEvent);
            streamSession.put(streamId, transaction);
@@ -573,24 +576,89 @@
    /**
     * 音视频录像控制
     * 
     * @param device  视频设备
     * @param channelId  预览通道
     * @param device        视频设备
     * @param channelId      预览通道
     * @param recordCmdStr    录像命令:Record / StopRecord
     */  
    @Override
    public boolean recordCmd(Device device, String channelId) {
        // TODO Auto-generated method stub
        return false;
    public boolean recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            }
            cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
            cmdXml.append("</Control>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromRecord" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 远程启动控制命令
     *
     * @param device    视频设备
     */
    @Override
    public boolean teleBootCmd(Device device) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            cmdXml.append("<TeleBoot>Boot</TeleBoot>\r\n");
            cmdXml.append("</Control>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromBoot" + tm, null);
            transmitRequest(device, request);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 报警布防/撤防命令
     * 
     * @param device  视频设备
     */
     * @param device          视频设备
     * @param guardCmdStr    "SetGuard"/"ResetGuard"
     */
    @Override
    public boolean guardCmd(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
            cmdXml.append("</Control>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromGuard" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
@@ -599,9 +667,37 @@
     * @param device  视频设备
     */  
    @Override
    public boolean alarmCmd(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            cmdXml.append("<AlarmCmd>ResetAlarm</AlarmCmd>\r\n");
            if (!XmlUtil.isEmpty(alarmMethod) || !XmlUtil.isEmpty(alarmType)) {
                cmdXml.append("<Info>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod)) {
                cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmType)) {
                cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod) || !XmlUtil.isEmpty(alarmType)) {
                cmdXml.append("</Info>\r\n");
            }
            cmdXml.append("</Control>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromAlarm" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
@@ -611,20 +707,79 @@
     * @param channelId  预览通道
     */ 
    @Override
    public boolean iFameCmd(Device device, String channelId) {
        // TODO Auto-generated method stub
        return false;
    public boolean iFrameCmd(Device device, String channelId) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            }
            cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
            cmdXml.append("</Control>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromBoot" + tm, null);
            transmitRequest(device, request);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 看守位控制命令
     * 
     * @param device  视频设备
     * @param device        视频设备
     * @param enabled        看守位使能:1 = 开启,0 = 关闭
     * @param resetTime        自动归位时间间隔,开启看守位时使用,单位:秒(s)
     * @param presetIndex    调用预置位编号,开启看守位时使用,取值范围0~255
     */  
    @Override
    public boolean homePositionCmd(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            }
            cmdXml.append("<HomePosition>\r\n");
            if (NumericUtil.isInteger(enabled) && (!enabled.equals("0"))) {
                cmdXml.append("<Enabled>1</Enabled>\r\n");
                if (NumericUtil.isInteger(resetTime)) {
                    cmdXml.append("<ResetTime>" + resetTime + "</ResetTime>\r\n");
                } else {
                    cmdXml.append("<ResetTime>0</ResetTime>\r\n");
                }
                if (NumericUtil.isInteger(presetIndex)) {
                    cmdXml.append("<PresetIndex>" + presetIndex + "</PresetIndex>\r\n");
                } else {
                    cmdXml.append("<PresetIndex>0</PresetIndex>\r\n");
                }
            } else {
                cmdXml.append("<Enabled>0</Enabled>\r\n");
            }
            cmdXml.append("</HomePosition>\r\n");
            cmdXml.append("</Control>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromGuard" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
@@ -639,14 +794,87 @@
    }
    /**
     * 设备配置命令:basicParam
     *
     * @param device              视频设备
     * @param channelId            通道编码(可选)
     * @param name                设备/通道名称(可选)
     * @param expiration        注册过期时间(可选)
     * @param heartBeatInterval    心跳间隔时间(可选)
     * @param heartBeatCount    心跳超时次数(可选)
     */
    @Override
    public boolean deviceBasicConfigCmd(Device device, String channelId, String name, String expiration,
                                        String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Control>\r\n");
            cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            }
            cmdXml.append("<BasicParam>\r\n");
            if (!XmlUtil.isEmpty(name)) {
                cmdXml.append("<Name>" + name + "</Name>\r\n");
            }
            if (NumericUtil.isInteger(expiration)) {
                if (Integer.valueOf(expiration) > 0) {
                    cmdXml.append("<Expiration>" + expiration + "</Expiration>\r\n");
                }
            }
            if (NumericUtil.isInteger(heartBeatInterval)) {
                if (Integer.valueOf(heartBeatInterval) > 0) {
                    cmdXml.append("<HeartBeatInterval>" + heartBeatInterval + "</HeartBeatInterval>\r\n");
                }
            }
            if (NumericUtil.isInteger(heartBeatCount)) {
                if (Integer.valueOf(heartBeatCount) > 0) {
                    cmdXml.append("<HeartBeatCount>" + heartBeatCount + "</HeartBeatCount>\r\n");
                }
            }
            cmdXml.append("</BasicParam>\r\n");
            cmdXml.append("</Control>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromConfig" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 查询设备状态
     * 
     * @param device 视频设备
     */  
    @Override
    public boolean deviceStatusQuery(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer catalogXml = new StringBuffer(200);
            catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            catalogXml.append("<Query>\r\n");
            catalogXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
            catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            catalogXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, "FromStatus" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
@@ -665,7 +893,8 @@
            catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            catalogXml.append("</Query>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", null);
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDev" + tm, null);
            transmitRequest(device, request);
            
@@ -694,7 +923,8 @@
            catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            catalogXml.append("</Query>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", null);
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCat" + tm, null);
            transmitRequest(device, request, errorEvent);
        } catch (SipException | ParseException | InvalidArgumentException e) {
@@ -728,7 +958,8 @@
            recordInfoXml.append("<Type>all</Type>\r\n");
            recordInfoXml.append("</Query>\r\n");
            
            Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", null);
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "fromRec" + tm, null);
            transmitRequest(device, request);
        } catch (SipException | ParseException | InvalidArgumentException e) {
@@ -741,23 +972,86 @@
    /**
     * 查询报警信息
     * 
     * @param device 视频设备
     */
     * @param device        视频设备
     * @param startPriority    报警起始级别(可选)
     * @param endPriority    报警终止级别(可选)
     * @param alarmMethods    报警方式条件(可选)
     * @param alarmType        报警类型
     * @param startTime        报警发生起始时间(可选)
     * @param endTime        报警发生终止时间(可选)
     * @return                true = 命令发送成功
     */
    @Override
    public boolean alarmInfoQuery(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType,
                                 String startTime, String endTime, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Query>\r\n");
            cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            if (!XmlUtil.isEmpty(startPriority)) {
                cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(endPriority)) {
                cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod)) {
                cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmType)) {
                cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
            }
            if (!XmlUtil.isEmpty(startTime)) {
                cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
            }
            if (!XmlUtil.isEmpty(endTime)) {
                cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
            }
            cmdXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromAlarm" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 查询设备配置
     * 
     * @param device 视频设备
     */
     * @param device         视频设备
     * @param channelId        通道编码(可选)
     * @param configType    配置类型:
     */
    @Override
    public boolean configQuery(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Query>\r\n");
            cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            }
            cmdXml.append("<ConfigType>" + configType + "</ConfigType>\r\n");
            cmdXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromConfig" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
@@ -766,9 +1060,28 @@
     * @param device 视频设备
     */  
    @Override
    public boolean presetQuery(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" ?>\r\n");
            cmdXml.append("<Query>\r\n");
            cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            if (XmlUtil.isEmpty(channelId)) {
                cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            } else {
                cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            }
            cmdXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, "FromConfig" + tm, null);
            transmitRequest(device, request, errorEvent);
            return true;
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
@@ -777,11 +1090,116 @@
     * @param device 视频设备
     */  
    @Override
    public boolean mobilePostitionQuery(Device device) {
        // TODO Auto-generated method stub
        return false;
    public boolean mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) {
        try {
            StringBuffer mobilePostitionXml = new StringBuffer(200);
            mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            mobilePostitionXml.append("<Query>\r\n");
            mobilePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
            mobilePostitionXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            mobilePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            mobilePostitionXml.append("<Interval>60</Interval>\r\n");
            mobilePostitionXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), "viaTagPos" + tm, "fromTagPos" + tm, null);
            transmitRequest(device, request, errorEvent);
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 订阅、取消订阅移动位置
     *
     * @param device    视频设备
     * @param expires    订阅超时时间
     * @param interval    上报时间间隔
     * @return            true = 命令发送成功
     */
    public boolean mobilePositionSubscribe(Device device, int expires, int interval) {
        try {
            StringBuffer subscribePostitionXml = new StringBuffer(200);
            subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            subscribePostitionXml.append("<Query>\r\n");
            subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
            subscribePostitionXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            if (expires > 0) {
                subscribePostitionXml.append("<Interval>" + String.valueOf(interval) + "</Interval>\r\n");
            }
            subscribePostitionXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), "viaTagPos" + tm, "fromTagPos" + tm, null, expires, "presence" ); //Position;id=" + tm.substring(tm.length() - 4));
            transmitRequest(device, request);
            return true;
        } catch ( NumberFormatException | ParseException | InvalidArgumentException    | SipException e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 订阅、取消订阅报警信息
     *
     * @param device        视频设备
     * @param expires        订阅过期时间(0 = 取消订阅)
     * @param startPriority    报警起始级别(可选)
     * @param endPriority    报警终止级别(可选)
     * @param alarmMethod    报警方式条件(可选)
     * @param alarmType        报警类型
     * @param startTime        报警发生起始时间(可选)
     * @param endTime        报警发生终止时间(可选)
     * @return                true = 命令发送成功
     */
    public boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) {
        try {
            StringBuffer cmdXml = new StringBuffer(200);
            cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            cmdXml.append("<Query>\r\n");
            cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
            cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            if (!XmlUtil.isEmpty(startPriority)) {
                cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(endPriority)) {
                cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmMethod)) {
                cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
            }
            if (!XmlUtil.isEmpty(alarmType)) {
                cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
            }
            if (!XmlUtil.isEmpty(startTime)) {
                cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
            }
            if (!XmlUtil.isEmpty(endTime)) {
                cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
            }
            cmdXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "viaTagPos" + tm, "fromTagPos" + tm, null, expires, "presence" );
            transmitRequest(device, request);
            return true;
        } catch ( NumberFormatException | ParseException | InvalidArgumentException    | SipException e) {
            e.printStackTrace();
            return false;
        }
    }
    private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
        return transmitRequest(device, request, null, null);
    }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -26,6 +26,10 @@
import org.springframework.beans.factory.annotation.Autowired;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -73,9 +77,12 @@
    private static final String MESSAGE_RECORD_INFO = "RecordInfo";
    private static final String MESSAGE_MEDIA_STATUS = "MediaStatus";
    // private static final String MESSAGE_BROADCAST = "Broadcast";
    // private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
    // private static final String MESSAGE_MOBILE_POSITION = "MobilePosition";
    private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
    private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl";
    private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig";
    private static final String MESSAGE_MOBILE_POSITION = "MobilePosition";
    // private static final String MESSAGE_MOBILE_POSITION_INTERVAL = "Interval";
    private static final String MESSAGE_PRESET_QUERY = "PresetQuery";
    /**
     * 处理MESSAGE请求
@@ -94,12 +101,22 @@
                processMessageKeepAlive(evt);
            } else if (MESSAGE_CONFIG_DOWNLOAD.equals(cmd)) {
                logger.info("接收到ConfigDownload消息");
                processMessageConfigDownload(evt);
            } else if (MESSAGE_CATALOG.equals(cmd)) {
                logger.info("接收到Catalog消息");
                processMessageCatalogList(evt);
            } else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
                logger.info("接收到DeviceInfo消息");
                processMessageDeviceInfo(evt);
            } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
                logger.info("接收到DeviceStatus消息");
                processMessageDeviceStatus(evt);
            } else if (MESSAGE_DEVICE_CONTROL.equals(cmd)) {
                logger.info("接收到DeviceControl消息");
                processMessageDeviceControl(evt);
            } else if (MESSAGE_DEVICE_CONFIG.equals(cmd)) {
                logger.info("接收到DeviceConfig消息");
                processMessageDeviceConfig(evt);
            } else if (MESSAGE_ALARM.equals(cmd)) {
                logger.info("接收到Alarm消息");
                processMessageAlarm(evt);
@@ -109,16 +126,236 @@
            }else if (MESSAGE_MEDIA_STATUS.equals(cmd)) {
                logger.info("接收到MediaStatus消息");
                processMessageMediaStatus(evt);
            } else if (MESSAGE_MOBILE_POSITION.equals(cmd)) {
                logger.info("接收到MobilePosition消息");
                processMessageMobilePosition(evt);
            } else if (MESSAGE_PRESET_QUERY.equals(cmd)) {
                logger.info("接收到PresetQuery消息");
                processMessagePresetQuery(evt);
            } else {
                logger.info("接收到消息:" + cmd);
                responseAck(evt);
            }
        } catch (DocumentException e) {
        } catch (DocumentException | SipException |InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
    }
    /**
     * 收到deviceInfo设备信息请求 处理
     * 处理MobilePosition移动位置消息
     *
     * @param evt
     */
    private void processMessageMobilePosition(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            MobilePosition mobilePosition = new MobilePosition();
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getTextTrim().toString();
            Device device = storager.queryVideoDevice(deviceId);
            if (device != null) {
                if (!StringUtils.isEmpty(device.getName())) {
                    mobilePosition.setDeviceName(device.getName());
                }
            }
            mobilePosition.setDeviceId(XmlUtil.getText(rootElement, "DeviceID"));
            mobilePosition.setTime(XmlUtil.getText(rootElement, "Time"));
            mobilePosition.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude")));
            mobilePosition.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude")));
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Speed"))) {
                mobilePosition.setSpeed(Double.parseDouble(XmlUtil.getText(rootElement, "Speed")));
            } else {
                mobilePosition.setSpeed(0.0);
            }
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Direction"))) {
                mobilePosition.setDirection(Double.parseDouble(XmlUtil.getText(rootElement, "Direction")));
            } else {
                mobilePosition.setDirection(0.0);
            }
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Altitude"))) {
                mobilePosition.setAltitude(Double.parseDouble(XmlUtil.getText(rootElement, "Altitude")));
            } else {
                mobilePosition.setAltitude(0.0);
            }
            mobilePosition.setReportSource("Mobile Position");
            BaiduPoint bp = new BaiduPoint();
            bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
            logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
            mobilePosition.setGeodeticSystem("BD-09");
            mobilePosition.setCnLng(bp.getBdLng());
            mobilePosition.setCnLat(bp.getBdLat());
            if (!userSetup.getSavePositionHistory()) {
                storager.clearMobilePositionsByDeviceId(deviceId);
            }
            storager.insertMobilePosition(mobilePosition);
            //回复 200 OK
            responseAck(evt);
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理DeviceStatus设备状态Message
     *
     * @param evt
     */
    private void processMessageDeviceStatus(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = XmlUtil.getText(rootElement, "DeviceID");
            // 检查设备是否存在, 不存在则不回复
            if (storager.exists(deviceId)) {
                // 回复200 OK
                responseAck(evt);
                JSONObject json = new JSONObject();
                XmlUtil.node2Json(rootElement, json);
                if (logger.isDebugEnabled()) {
                    logger.debug(json.toJSONString());
                }
                RequestMessage msg = new RequestMessage();
                msg.setDeviceId(deviceId);
                msg.setType(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS);
                msg.setData(json);
                deferredResultHolder.invokeResult(msg);
                if (offLineDetector.isOnline(deviceId)) {
                    publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
                } else {
                }
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理DeviceControl设备状态Message
     *
     * @param evt
     */
    private void processMessageDeviceControl(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = XmlUtil.getText(rootElement, "DeviceID");
            //String result = XmlUtil.getText(rootElement, "Result");
            // 回复200 OK
            responseAck(evt);
            if (rootElement.getName().equals("Response")) {//} !XmlUtil.isEmpty(result)) {
                // 此处是对本平台发出DeviceControl指令的应答
                JSONObject json = new JSONObject();
                XmlUtil.node2Json(rootElement, json);
                if (logger.isDebugEnabled()) {
                    logger.debug(json.toJSONString());
                }
                RequestMessage msg = new RequestMessage();
                msg.setDeviceId(deviceId);
                msg.setType(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL);
                msg.setData(json);
                deferredResultHolder.invokeResult(msg);
            } else {
                // 此处是上级发出的DeviceControl指令
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理DeviceConfig设备状态Message
     *
     * @param evt
     */
    private void processMessageDeviceConfig(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = XmlUtil.getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);
            if (rootElement.getName().equals("Response")) {
                    // 此处是对本平台发出DeviceControl指令的应答
                JSONObject json = new JSONObject();
                XmlUtil.node2Json(rootElement, json);
                if (logger.isDebugEnabled()) {
                    logger.debug(json.toJSONString());
                }
                RequestMessage msg = new RequestMessage();
                msg.setDeviceId(deviceId);
                msg.setType(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG);
                msg.setData(json);
                deferredResultHolder.invokeResult(msg);
            } else {
                // 此处是上级发出的DeviceConfig指令
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理ConfigDownload设备状态Message
     *
     * @param evt
     */
    private void processMessageConfigDownload(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = XmlUtil.getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);
            if (rootElement.getName().equals("Response")) {
                    // 此处是对本平台发出DeviceControl指令的应答
                JSONObject json = new JSONObject();
                XmlUtil.node2Json(rootElement, json);
                if (logger.isDebugEnabled()) {
                    logger.debug(json.toJSONString());
                }
                RequestMessage msg = new RequestMessage();
                msg.setDeviceId(deviceId);
                msg.setType(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD);
                msg.setData(json);
                deferredResultHolder.invokeResult(msg);
            } else {
                // 此处是上级发出的DeviceConfig指令
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理PresetQuery预置位列表Message
     *
     * @param evt
     */
    private void processMessagePresetQuery(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = XmlUtil.getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);
            if (rootElement.getName().equals("Response")) {//   !XmlUtil.isEmpty(result)) {
                // 此处是对本平台发出DeviceControl指令的应答
                JSONObject json = new JSONObject();
                XmlUtil.node2Json(rootElement, json);
                if (logger.isDebugEnabled()) {
                    logger.debug(json.toJSONString());
                }
                RequestMessage msg = new RequestMessage();
                msg.setDeviceId(deviceId);
                msg.setType(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY);
                msg.setData(json);
                deferredResultHolder.invokeResult(msg);
            } else {
                // 此处是上级发出的DeviceControl指令
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理DeviceInfo设备信息Message
     * 
     * @param evt
     */
@@ -126,7 +363,7 @@
        try {
            Element rootElement = getRootElement(evt);
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getText().toString();
            String deviceId = deviceIdElement.getTextTrim().toString();
            Device device = storager.queryVideoDevice(deviceId);
            if (device == null) {
@@ -228,80 +465,78 @@
                            deviceChannel.setStatus(0);
                        }
                        deviceChannel.setManufacture(XmlUtil.getText(itemDevice, "Manufacturer"));
                        deviceChannel.setModel(XmlUtil.getText(itemDevice, "Model"));
                        deviceChannel.setOwner(XmlUtil.getText(itemDevice, "Owner"));
                        deviceChannel.setCivilCode(XmlUtil.getText(itemDevice, "CivilCode"));
                        deviceChannel.setBlock(XmlUtil.getText(itemDevice, "Block"));
                        deviceChannel.setAddress(XmlUtil.getText(itemDevice, "Address"));
                        if (XmlUtil.getText(itemDevice, "Parental") == null || XmlUtil.getText(itemDevice, "Parental") == "") {
                            deviceChannel.setParental(0);
                        } else {
                            deviceChannel.setParental(Integer.parseInt(XmlUtil.getText(itemDevice, "Parental")));
                        }
                        deviceChannel.setParentId(XmlUtil.getText(itemDevice, "ParentID"));
                        if (XmlUtil.getText(itemDevice, "SafetyWay") == null || XmlUtil.getText(itemDevice, "SafetyWay")== "") {
                            deviceChannel.setSafetyWay(0);
                        } else {
                            deviceChannel.setSafetyWay(Integer.parseInt(XmlUtil.getText(itemDevice, "SafetyWay")));
                        }
                        if (XmlUtil.getText(itemDevice, "RegisterWay") == null || XmlUtil.getText(itemDevice, "RegisterWay") =="") {
                            deviceChannel.setRegisterWay(1);
                        } else {
                            deviceChannel.setRegisterWay(Integer.parseInt(XmlUtil.getText(itemDevice, "RegisterWay")));
                        }
                        deviceChannel.setCertNum(XmlUtil.getText(itemDevice, "CertNum"));
                        if (XmlUtil.getText(itemDevice, "Certifiable") == null || XmlUtil.getText(itemDevice, "Certifiable") == "") {
                            deviceChannel.setCertifiable(0);
                        } else {
                            deviceChannel.setCertifiable(Integer.parseInt(XmlUtil.getText(itemDevice, "Certifiable")));
                        }
                        if (XmlUtil.getText(itemDevice, "ErrCode") == null || XmlUtil.getText(itemDevice, "ErrCode") == "") {
                            deviceChannel.setErrCode(0);
                        } else {
                            deviceChannel.setErrCode(Integer.parseInt(XmlUtil.getText(itemDevice, "ErrCode")));
                        }
                        deviceChannel.setEndTime(XmlUtil.getText(itemDevice, "EndTime"));
                        deviceChannel.setSecrecy(XmlUtil.getText(itemDevice, "Secrecy"));
                        deviceChannel.setIpAddress(XmlUtil.getText(itemDevice, "IPAddress"));
                        if (XmlUtil.getText(itemDevice, "Port") == null || XmlUtil.getText(itemDevice, "Port") =="") {
                            deviceChannel.setPort(0);
                        } else {
                            deviceChannel.setPort(Integer.parseInt(XmlUtil.getText(itemDevice, "Port")));
                        }
                        deviceChannel.setPassword(XmlUtil.getText(itemDevice, "Password"));
                        if (XmlUtil.getText(itemDevice, "Longitude") == null || XmlUtil.getText(itemDevice, "Longitude") == "") {
                            deviceChannel.setLongitude(0.00);
                        } else {
                            deviceChannel.setLongitude(Double.parseDouble(XmlUtil.getText(itemDevice, "Longitude")));
                        }
                        if (XmlUtil.getText(itemDevice, "Latitude") == null || XmlUtil.getText(itemDevice, "Latitude") =="") {
                            deviceChannel.setLatitude(0.00);
                        } else {
                            deviceChannel.setLatitude(Double.parseDouble(XmlUtil.getText(itemDevice, "Latitude")));
                        }
                        if (XmlUtil.getText(itemDevice, "PTZType") == null || XmlUtil.getText(itemDevice, "PTZType") == "") {
                            deviceChannel.setPTZType(0);
                        } else {
                            deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType")));
                        }
                        deviceChannel.setHasAudio(true); // 默认含有音频,播放时再检查是否有音频及是否AAC
                        storager.updateChannel(device.getDeviceId(), deviceChannel);
                    deviceChannel.setManufacture(XmlUtil.getText(itemDevice, "Manufacturer"));
                    deviceChannel.setModel(XmlUtil.getText(itemDevice, "Model"));
                    deviceChannel.setOwner(XmlUtil.getText(itemDevice, "Owner"));
                    deviceChannel.setCivilCode(XmlUtil.getText(itemDevice, "CivilCode"));
                    deviceChannel.setBlock(XmlUtil.getText(itemDevice, "Block"));
                    deviceChannel.setAddress(XmlUtil.getText(itemDevice, "Address"));
                    if (XmlUtil.getText(itemDevice, "Parental") == null || XmlUtil.getText(itemDevice, "Parental") == "") {
                        deviceChannel.setParental(0);
                    } else {
                        deviceChannel.setParental(Integer.parseInt(XmlUtil.getText(itemDevice, "Parental")));
                    }
                    deviceChannel.setParentId(XmlUtil.getText(itemDevice, "ParentID"));
                    if (XmlUtil.getText(itemDevice, "SafetyWay") == null || XmlUtil.getText(itemDevice, "SafetyWay")== "") {
                        deviceChannel.setSafetyWay(0);
                    } else {
                        deviceChannel.setSafetyWay(Integer.parseInt(XmlUtil.getText(itemDevice, "SafetyWay")));
                    }
                    if (XmlUtil.getText(itemDevice, "RegisterWay") == null || XmlUtil.getText(itemDevice, "RegisterWay") =="") {
                        deviceChannel.setRegisterWay(1);
                    } else {
                        deviceChannel.setRegisterWay(Integer.parseInt(XmlUtil.getText(itemDevice, "RegisterWay")));
                    }
                    deviceChannel.setCertNum(XmlUtil.getText(itemDevice, "CertNum"));
                    if (XmlUtil.getText(itemDevice, "Certifiable") == null || XmlUtil.getText(itemDevice, "Certifiable") == "") {
                        deviceChannel.setCertifiable(0);
                    } else {
                        deviceChannel.setCertifiable(Integer.parseInt(XmlUtil.getText(itemDevice, "Certifiable")));
                    }
                    if (XmlUtil.getText(itemDevice, "ErrCode") == null || XmlUtil.getText(itemDevice, "ErrCode") == "") {
                        deviceChannel.setErrCode(0);
                    } else {
                        deviceChannel.setErrCode(Integer.parseInt(XmlUtil.getText(itemDevice, "ErrCode")));
                    }
                    deviceChannel.setEndTime(XmlUtil.getText(itemDevice, "EndTime"));
                    deviceChannel.setSecrecy(XmlUtil.getText(itemDevice, "Secrecy"));
                    deviceChannel.setIpAddress(XmlUtil.getText(itemDevice, "IPAddress"));
                    if (XmlUtil.getText(itemDevice, "Port") == null || XmlUtil.getText(itemDevice, "Port") =="") {
                        deviceChannel.setPort(0);
                    } else {
                        deviceChannel.setPort(Integer.parseInt(XmlUtil.getText(itemDevice, "Port")));
                    }
                    deviceChannel.setPassword(XmlUtil.getText(itemDevice, "Password"));
                    if (XmlUtil.getText(itemDevice, "Longitude") == null || XmlUtil.getText(itemDevice, "Longitude") == "") {
                        deviceChannel.setLongitude(0.00);
                    } else {
                        deviceChannel.setLongitude(Double.parseDouble(XmlUtil.getText(itemDevice, "Longitude")));
                    }
                    if (XmlUtil.getText(itemDevice, "Latitude") == null || XmlUtil.getText(itemDevice, "Latitude") =="") {
                        deviceChannel.setLatitude(0.00);
                    } else {
                        deviceChannel.setLatitude(Double.parseDouble(XmlUtil.getText(itemDevice, "Latitude")));
                    }
                    if (XmlUtil.getText(itemDevice, "PTZType") == null || XmlUtil.getText(itemDevice, "PTZType") == "") {
                        deviceChannel.setPTZType(0);
                    } else {
                        deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType")));
                    }
                    deviceChannel.setHasAudio(true); // 默认含有音频,播放时再检查是否有音频及是否AAC
                    storager.updateChannel(device.getDeviceId(), deviceChannel);
                }
                    RequestMessage msg = new RequestMessage();
                    msg.setDeviceId(deviceId);
                    msg.setType(DeferredResultHolder.CALLBACK_CMD_CATALOG);
                    msg.setData(device);
                    deferredResultHolder.invokeResult(msg);
                    // 回复200 OK
                    responseAck(evt);
                    if (offLineDetector.isOnline(deviceId)) {
                        publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
                    }
                RequestMessage msg = new RequestMessage();
                msg.setDeviceId(deviceId);
                msg.setType(DeferredResultHolder.CALLBACK_CMD_CATALOG);
                msg.setData(device);
                deferredResultHolder.invokeResult(msg);
                // 回复200 OK
                responseAck(evt);
                if (offLineDetector.isOnline(deviceId)) {
                    publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
                }
            }
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
@@ -324,41 +559,19 @@
                // storager.queryChannel(deviceId)
                return;
            }
            DeviceAlarm deviceAlarm = new DeviceAlarm();
            deviceAlarm.setDeviceId(deviceId);
            deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority"));
            deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod"));
            deviceAlarm.setAlarmTime(XmlUtil.getText(rootElement, "AlarmTime"));
            if (XmlUtil.getText(rootElement, "AlarmDescription") == null) {
                deviceAlarm.setAlarmDescription("");
            } else {
                deviceAlarm.setAlarmDescription(XmlUtil.getText(rootElement, "AlarmDescription"));
            device.setName(XmlUtil.getText(rootElement, "DeviceName"));
            device.setManufacturer(XmlUtil.getText(rootElement, "Manufacturer"));
            device.setModel(XmlUtil.getText(rootElement, "Model"));
            device.setFirmware(XmlUtil.getText(rootElement, "Firmware"));
            if (StringUtils.isEmpty(device.getStreamMode())) {
                device.setStreamMode("UDP");
            }
            if (XmlUtil.getText(rootElement, "Longitude") == null || XmlUtil.getText(rootElement, "Longitude") == "") {
                deviceAlarm.setLongitude(0.00);
            } else {
                deviceAlarm.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude")));
            }
            if (XmlUtil.getText(rootElement, "Latitude") == null || XmlUtil.getText(rootElement, "Latitude") =="") {
                deviceAlarm.setLatitude(0.00);
            } else {
                deviceAlarm.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude")));
            }
            // device.setName(XmlUtil.getText(rootElement, "DeviceName"));
            // device.setManufacturer(XmlUtil.getText(rootElement, "Manufacturer"));
            // device.setModel(XmlUtil.getText(rootElement, "Model"));
            // device.setFirmware(XmlUtil.getText(rootElement, "Firmware"));
            // if (StringUtils.isEmpty(device.getStreamMode())) {
            //     device.setStreamMode("UDP");
            // }
            // storager.updateDevice(device);
            storager.updateDevice(device);
            //cmder.catalogQuery(device, null);
            // 回复200 OK
            responseAck(evt);
            if (offLineDetector.isOnline(deviceId)) {
                publisher.deviceAlarmEventPublish(deviceAlarm);
                publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
            }
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            // } catch (DocumentException e) {
@@ -384,14 +597,13 @@
                } else {
                }
            }
        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
            e.printStackTrace();
        }
    }
    /***
     * 收到catalog设备目录列表请求 处理 TODO 过期时间暂时写死180秒,后续与DeferredResult超时时间保持一致
     * 处理RecordInfo设备录像列表Message请求 TODO 过期时间暂时写死180秒,后续与DeferredResult超时时间保持一致
     * 
     * @param evt
     */
@@ -415,8 +627,11 @@
            Element recordListElement = rootElement.element("RecordList");
            if (recordListElement == null || recordInfo.getSumNum() == 0) {
                logger.info("无录像数据");
                // responseAck(evt);
                // return;
                RequestMessage msg = new RequestMessage();
                msg.setDeviceId(deviceId);
                msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
                msg.setData(recordInfo);
                deferredResultHolder.invokeResult(msg);
            } else {
                Iterator<Element> recordListIterator = recordListElement.elementIterator();
                List<RecordItem> recordList = new ArrayList<RecordItem>();
@@ -446,53 +661,73 @@
                        record.setRecorderId(XmlUtil.getText(itemRecord, "RecorderID"));
                        recordList.add(record);
                    }
                    // recordList.sort(Comparator.naturalOrder());
                    recordInfo.setRecordList(recordList);
                }
                // 存在录像且如果当前录像明细个数小于总条数,说明拆包返回,需要组装,暂不返回
                if (recordInfo.getSumNum() > 0 && recordList.size() > 0 && recordList.size() < recordInfo.getSumNum()) {
                    // 为防止连续请求该设备的录像数据,返回数据错乱,特增加sn进行区分
                    String cacheKey = CACHE_RECORDINFO_KEY + deviceId + sn;
                    redis.set(cacheKey + "_" + uuid, recordList, 90);
                    List<Object> cacheKeys = redis.scan(cacheKey + "_*");
                    List<RecordItem> totalRecordList = new ArrayList<RecordItem>();
                    for (int i = 0; i < cacheKeys.size(); i++) {
                        totalRecordList.addAll((List<RecordItem>) redis.get(cacheKeys.get(i).toString()));
                // 改用单独线程统计已获取录像文件数量,避免多包并行分别统计不完整的问题
                String cacheKey = CACHE_RECORDINFO_KEY + deviceId + sn;
                redis.set(cacheKey + "_" + uuid, recordList, 90);
                if (!threadNameList.contains(cacheKey)) {
                    threadNameList.add(cacheKey);
                    CheckForAllRecordsThread chk = new CheckForAllRecordsThread(cacheKey, recordInfo);
                    chk.setName(cacheKey);
                    chk.setDeferredResultHolder(deferredResultHolder);
                    chk.setRedis(redis);
                    chk.setLogger(logger);
                    chk.start();
                    if (logger.isDebugEnabled()) {
                        logger.debug("Start Thread " + cacheKey + ".");
                    }
                    if (totalRecordList.size() < recordInfo.getSumNum()) {
                        logger.info("已获取" + totalRecordList.size() + "项录像数据,共" + recordInfo.getSumNum() + "项");
                        return;
                    }
                    logger.info("录像数据已全部获取,共" + recordInfo.getSumNum() + "项");
                    recordInfo.setRecordList(totalRecordList);
                    for (int i = 0; i < cacheKeys.size(); i++) {
                        redis.del(cacheKeys.get(i).toString());
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Thread " + cacheKey + " already started.");
                    }
                }
                // 自然顺序排序, 元素进行升序排列
                recordInfo.getRecordList().sort(Comparator.naturalOrder());
                // 存在录像且如果当前录像明细个数小于总条数,说明拆包返回,需要组装,暂不返回
                // if (recordInfo.getSumNum() > 0 && recordList.size() > 0 && recordList.size() < recordInfo.getSumNum()) {
                //     // 为防止连续请求该设备的录像数据,返回数据错乱,特增加sn进行区分
                //     String cacheKey = CACHE_RECORDINFO_KEY + deviceId + sn;
                //     redis.set(cacheKey + "_" + uuid, recordList, 90);
                //     List<Object> cacheKeys = redis.scan(cacheKey + "_*");
                //     List<RecordItem> totalRecordList = new ArrayList<RecordItem>();
                //     for (int i = 0; i < cacheKeys.size(); i++) {
                //         totalRecordList.addAll((List<RecordItem>) redis.get(cacheKeys.get(i).toString()));
                //     }
                //     if (totalRecordList.size() < recordInfo.getSumNum()) {
                //         logger.info("已获取" + totalRecordList.size() + "项录像数据,共" + recordInfo.getSumNum() + "项");
                //         return;
                //     }
                //     logger.info("录像数据已全部获取,共" + recordInfo.getSumNum() + "项");
                //     recordInfo.setRecordList(totalRecordList);
                //     for (int i = 0; i < cacheKeys.size(); i++) {
                //         redis.del(cacheKeys.get(i).toString());
                //     }
                // }
                // // 自然顺序排序, 元素进行升序排列
                // recordInfo.getRecordList().sort(Comparator.naturalOrder());
            }
            // 走到这里,有以下可能:1、没有录像信息,第一次收到recordinfo的消息即返回响应数据,无redis操作
            // 2、有录像数据,且第一次即收到完整数据,返回响应数据,无redis操作
            // 3、有录像数据,在超时时间内收到多次包组装后数量足够,返回数据
            // 对记录进行排序
            RequestMessage msg = new RequestMessage();
            msg.setDeviceId(deviceId);
            msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
            // // 自然顺序排序, 元素进行升序排列
            // recordInfo.getRecordList().sort(Comparator.naturalOrder());
            msg.setData(recordInfo);
            deferredResultHolder.invokeResult(msg);
            logger.info("处理完成,返回结果");
            // RequestMessage msg = new RequestMessage();
            // msg.setDeviceId(deviceId);
            // msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
            // msg.setData(recordInfo);
            // deferredResultHolder.invokeResult(msg);
            // logger.info("处理完成,返回结果");
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
    }
    /**
     * 收到MediaStatus消息处理
      *
      * @param evt
      */
    private void processMessageMediaStatus(RequestEvent evt){
        try {
            // 回复200 OK
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/NotifyRequestProcessor.java
New file
@@ -0,0 +1,384 @@
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.util.Iterator;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.message.Request;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.BaiduPoint;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.GpsUtil;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
 * @Description: Notify请求处理器
 * @author: lawrencehj
 * @date: 2021年1月27日
 */
public class NotifyRequestProcessor extends SIPRequestAbstractProcessor {
    private UserSetup userSetup = (UserSetup) SpringBeanFactory.getBean("userSetup");
    private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
    private IVideoManagerStorager storager;
    private IRedisCatchStorage redisCatchStorage;
    private EventPublisher publisher;
    private DeviceOffLineDetector offLineDetector;
    private static final String NOTIFY_CATALOG = "Catalog";
    private static final String NOTIFY_ALARM = "Alarm";
    private static final String NOTIFY_MOBILE_POSITION = "MobilePosition";
    @Override
    public void process(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String cmd = XmlUtil.getText(rootElement, "CmdType");
            if (NOTIFY_CATALOG.equals(cmd)) {
                logger.info("接收到Catalog通知");
                processNotifyCatalogList(evt);
            } else if (NOTIFY_ALARM.equals(cmd)) {
                logger.info("接收到Alarm通知");
                processNotifyAlarm(evt);
            } else if (NOTIFY_MOBILE_POSITION.equals(cmd)) {
                logger.info("接收到MobilePosition通知");
                processNotifyMobilePosition(evt);
            } else {
                logger.info("接收到消息:" + cmd);
                response200Ok(evt);
            }
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理MobilePosition移动位置Notify
     *
     * @param evt
     */
    private void processNotifyMobilePosition(RequestEvent evt) {
        try {
            // 回复 200 OK
            Element rootElement = getRootElement(evt);
            MobilePosition mobilePosition = new MobilePosition();
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getTextTrim().toString();
            Device device = storager.queryVideoDevice(deviceId);
            if (device != null) {
                if (!StringUtils.isEmpty(device.getName())) {
                    mobilePosition.setDeviceName(device.getName());
                }
            }
            mobilePosition.setDeviceId(XmlUtil.getText(rootElement, "DeviceID"));
            mobilePosition.setTime(XmlUtil.getText(rootElement, "Time"));
            mobilePosition.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude")));
            mobilePosition.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude")));
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Speed"))) {
                mobilePosition.setSpeed(Double.parseDouble(XmlUtil.getText(rootElement, "Speed")));
            } else {
                mobilePosition.setSpeed(0.0);
            }
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Direction"))) {
                mobilePosition.setDirection(Double.parseDouble(XmlUtil.getText(rootElement, "Direction")));
            } else {
                mobilePosition.setDirection(0.0);
            }
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Altitude"))) {
                mobilePosition.setAltitude(Double.parseDouble(XmlUtil.getText(rootElement, "Altitude")));
            } else {
                mobilePosition.setAltitude(0.0);
            }
            mobilePosition.setReportSource("Mobile Position");
            BaiduPoint bp = new BaiduPoint();
            bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
            logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
            mobilePosition.setGeodeticSystem("BD-09");
            mobilePosition.setCnLng(bp.getBdLng());
            mobilePosition.setCnLat(bp.getBdLat());
            if (!userSetup.getSavePositionHistory()) {
                storager.clearMobilePositionsByDeviceId(deviceId);
            }
            storager.insertMobilePosition(mobilePosition);
            response200Ok(evt);
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
    }
    /***
     * 处理alarm设备报警Notify
     *
     * @param evt
     */
    private void processNotifyAlarm(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getText().toString();
            Device device = storager.queryVideoDevice(deviceId);
            if (device == null) {
                return;
            }
            DeviceAlarm deviceAlarm = new DeviceAlarm();
            deviceAlarm.setDeviceId(deviceId);
            deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority"));
            deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod"));
            deviceAlarm.setAlarmTime(XmlUtil.getText(rootElement, "AlarmTime"));
            if (XmlUtil.getText(rootElement, "AlarmDescription") == null) {
                deviceAlarm.setAlarmDescription("");
            } else {
                deviceAlarm.setAlarmDescription(XmlUtil.getText(rootElement, "AlarmDescription"));
            }
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Longitude"))) {
                deviceAlarm.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude")));
            } else {
                deviceAlarm.setLongitude(0.00);
            }
            if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Latitude"))) {
                deviceAlarm.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude")));
            } else {
                deviceAlarm.setLatitude(0.00);
            }
            if (deviceAlarm.getAlarmMethod().equals("4")) {
                MobilePosition mobilePosition = new MobilePosition();
                mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
                mobilePosition.setTime(deviceAlarm.getAlarmTime());
                mobilePosition.setLongitude(deviceAlarm.getLongitude());
                mobilePosition.setLatitude(deviceAlarm.getLatitude());
                mobilePosition.setReportSource("GPS Alarm");
                BaiduPoint bp = new BaiduPoint();
                bp = GpsUtil.Wgs84ToBd09(String.valueOf(mobilePosition.getLongitude()), String.valueOf(mobilePosition.getLatitude()));
                logger.info("百度坐标:" + bp.getBdLng() + ", " + bp.getBdLat());
                mobilePosition.setGeodeticSystem("BD-09");
                mobilePosition.setCnLng(bp.getBdLng());
                mobilePosition.setCnLat(bp.getBdLat());
                if (!userSetup.getSavePositionHistory()) {
                    storager.clearMobilePositionsByDeviceId(deviceId);
                }
                storager.insertMobilePosition(mobilePosition);
            }
            // TODO: 需要实现存储报警信息、报警分类
            // 回复200 OK
            response200Ok(evt);
            if (offLineDetector.isOnline(deviceId)) {
                publisher.deviceAlarmEventPublish(deviceAlarm);
            }
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
    }
    /***
     * 处理catalog设备目录列表Notify
     *
     * @param evt
     */
    private void processNotifyCatalogList(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getText();
            Element deviceListElement = rootElement.element("DeviceList");
            if (deviceListElement == null) {
                return;
            }
            Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
            if (deviceListIterator != null) {
                Device device = storager.queryVideoDevice(deviceId);
                if (device == null) {
                    return;
                }
                // 遍历DeviceList
                while (deviceListIterator.hasNext()) {
                    Element itemDevice = deviceListIterator.next();
                    Element channelDeviceElement = itemDevice.element("DeviceID");
                    if (channelDeviceElement == null) {
                        continue;
                    }
                    String channelDeviceId = channelDeviceElement.getTextTrim();
                    Element channdelNameElement = itemDevice.element("Name");
                    String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : "";
                    Element statusElement = itemDevice.element("Status");
                    String status = statusElement != null ? statusElement.getTextTrim().toString() : "ON";
                    DeviceChannel deviceChannel = new DeviceChannel();
                    deviceChannel.setName(channelName);
                    deviceChannel.setChannelId(channelDeviceId);
                    // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理
                    if (status.equals("ON") || status.equals("On") || status.equals("ONLINE")) {
                        deviceChannel.setStatus(1);
                    }
                    if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) {
                        deviceChannel.setStatus(0);
                    }
                    deviceChannel.setManufacture(XmlUtil.getText(itemDevice, "Manufacturer"));
                    deviceChannel.setModel(XmlUtil.getText(itemDevice, "Model"));
                    deviceChannel.setOwner(XmlUtil.getText(itemDevice, "Owner"));
                    deviceChannel.setCivilCode(XmlUtil.getText(itemDevice, "CivilCode"));
                    deviceChannel.setBlock(XmlUtil.getText(itemDevice, "Block"));
                    deviceChannel.setAddress(XmlUtil.getText(itemDevice, "Address"));
                    if (XmlUtil.getText(itemDevice, "Parental") == null
                            || XmlUtil.getText(itemDevice, "Parental") == "") {
                        deviceChannel.setParental(0);
                    } else {
                        deviceChannel.setParental(Integer.parseInt(XmlUtil.getText(itemDevice, "Parental")));
                    }
                    deviceChannel.setParentId(XmlUtil.getText(itemDevice, "ParentID"));
                    if (XmlUtil.getText(itemDevice, "SafetyWay") == null
                            || XmlUtil.getText(itemDevice, "SafetyWay") == "") {
                        deviceChannel.setSafetyWay(0);
                    } else {
                        deviceChannel.setSafetyWay(Integer.parseInt(XmlUtil.getText(itemDevice, "SafetyWay")));
                    }
                    if (XmlUtil.getText(itemDevice, "RegisterWay") == null
                            || XmlUtil.getText(itemDevice, "RegisterWay") == "") {
                        deviceChannel.setRegisterWay(1);
                    } else {
                        deviceChannel.setRegisterWay(Integer.parseInt(XmlUtil.getText(itemDevice, "RegisterWay")));
                    }
                    deviceChannel.setCertNum(XmlUtil.getText(itemDevice, "CertNum"));
                    if (XmlUtil.getText(itemDevice, "Certifiable") == null
                            || XmlUtil.getText(itemDevice, "Certifiable") == "") {
                        deviceChannel.setCertifiable(0);
                    } else {
                        deviceChannel.setCertifiable(Integer.parseInt(XmlUtil.getText(itemDevice, "Certifiable")));
                    }
                    if (XmlUtil.getText(itemDevice, "ErrCode") == null
                            || XmlUtil.getText(itemDevice, "ErrCode") == "") {
                        deviceChannel.setErrCode(0);
                    } else {
                        deviceChannel.setErrCode(Integer.parseInt(XmlUtil.getText(itemDevice, "ErrCode")));
                    }
                    deviceChannel.setEndTime(XmlUtil.getText(itemDevice, "EndTime"));
                    deviceChannel.setSecrecy(XmlUtil.getText(itemDevice, "Secrecy"));
                    deviceChannel.setIpAddress(XmlUtil.getText(itemDevice, "IPAddress"));
                    if (XmlUtil.getText(itemDevice, "Port") == null || XmlUtil.getText(itemDevice, "Port") == "") {
                        deviceChannel.setPort(0);
                    } else {
                        deviceChannel.setPort(Integer.parseInt(XmlUtil.getText(itemDevice, "Port")));
                    }
                    deviceChannel.setPassword(XmlUtil.getText(itemDevice, "Password"));
                    if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {
                        deviceChannel.setLongitude(Double.parseDouble(XmlUtil.getText(itemDevice, "Longitude")));
                    } else {
                        deviceChannel.setLongitude(0.00);
                    }
                    if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Latitude"))) {
                        deviceChannel.setLatitude(Double.parseDouble(XmlUtil.getText(itemDevice, "Latitude")));
                    } else {
                        deviceChannel.setLatitude(0.00);
                    }
                    if (XmlUtil.getText(itemDevice, "PTZType") == null
                            || XmlUtil.getText(itemDevice, "PTZType") == "") {
                        deviceChannel.setPTZType(0);
                    } else {
                        deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType")));
                    }
                    deviceChannel.setHasAudio(true); // 默认含有音频,播放时再检查是否有音频及是否AAC
                    storager.updateChannel(device.getDeviceId(), deviceChannel);
                }
                // RequestMessage msg = new RequestMessage();
                // msg.setDeviceId(deviceId);
                // msg.setType(DeferredResultHolder.CALLBACK_CMD_CATALOG);
                // msg.setData(device);
                // deferredResultHolder.invokeResult(msg);
                // 回复200 OK
                response200Ok(evt);
                if (offLineDetector.isOnline(deviceId)) {
                    publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
                }
            }
        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
        }
    }
    /***
     * 回复200 OK
     *
     * @param evt
     * @throws SipException
     * @throws InvalidArgumentException
     * @throws ParseException
     */
    private void response200Ok(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
        Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
        getServerTransaction(evt).sendResponse(response);
    }
    private Element getRootElement(RequestEvent evt) throws DocumentException {
        Request request = evt.getRequest();
        SAXReader reader = new SAXReader();
        reader.setEncoding("gbk");
        Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
        return xml.getRootElement();
    }
    public void setCmder(SIPCommander cmder) {
    }
    public void setStorager(IVideoManagerStorager storager) {
        this.storager = storager;
    }
    public void setPublisher(EventPublisher publisher) {
        this.publisher = publisher;
    }
    public void setRedis(RedisUtil redis) {
    }
    public void setDeferredResultHolder(DeferredResultHolder deferredResultHolder) {
    }
    public void setOffLineDetector(DeviceOffLineDetector offLineDetector) {
        this.offLineDetector = offLineDetector;
    }
    public IRedisCatchStorage getRedisCatchStorage() {
        return redisCatchStorage;
    }
    public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
        this.redisCatchStorage = redisCatchStorage;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/OtherRequestProcessor.java
@@ -21,7 +21,7 @@
     */  
    @Override
    public void process(RequestEvent evt) {
        System.out.println("no support the method! Method:" + evt.getRequest().getMethod());
        System.out.println("Unsupported the method: " + evt.getRequest().getMethod());
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
@@ -145,7 +145,7 @@
            // 下发catelog查询目录
            if (registerFlag == 1 && device != null) {
                logger.info("注册成功! deviceId:" + device.getDeviceId());
                boolean exists = storager.exists(device.getDeviceId());
                // boolean exists = storager.exists(device.getDeviceId());
                device.setRegisterTimeMillis(System.currentTimeMillis());
                storager.updateDevice(device);
                publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java
@@ -12,14 +12,12 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
import gov.nist.javax.sip.header.CSeq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorFactory;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
@@ -31,7 +29,7 @@
@Component
public class InviteResponseProcessor implements ISIPResponseProcessor {
    private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
    // private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
    /**
     * 处理invite响应
src/main/java/com/genersoft/iot/vmp/gb28181/utils/NumericUtil.java
New file
@@ -0,0 +1,41 @@
package com.genersoft.iot.vmp.gb28181.utils;
/**
 * 数值格式判断和处理
 * @author lawrencehj
 * @date 2021年1月27日
 */
public class NumericUtil {
    /**
     * 判断是否Double格式
     * @param str
     * @return true/false
     */
    public static boolean isDouble(String str) {
        try {
            Double num2 = Double.valueOf(str);
            System.out.println(num2 + " is a valid numeric string!");
            return true;
        } catch (Exception e) {
            System.out.println(str + " is an invalid numeric string!");
            return false;
        }
    }
    /**
     * 判断是否Double格式
     * @param str
     * @return true/false
     */
    public static boolean isInteger(String str) {
        try {
            int num2 = Integer.valueOf(str);
            System.out.println(num2 + " is an integer!");
            return true;
        } catch (Exception e) {
            System.out.println(str + " is not an integer!");
            return false;
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
@@ -7,6 +7,9 @@
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
@@ -20,8 +23,7 @@
 * 
 * 
 */
public class XmlUtil
{
public class XmlUtil {
    /**
     * 日志服务
     */
@@ -30,22 +32,18 @@
    /**
     * 解析XML为Document对象
     * 
     * @param xml
     *            被解析的XMl
     * @param xml 被解析的XMl
     *
     * @return Document
     */
    public static Element parseXml(String xml)
    {
    public static Element parseXml(String xml) {
        Document document = null;
        //
        StringReader sr = new StringReader(xml);
        SAXReader saxReader = new SAXReader();
        try
        {
        try {
            document = saxReader.read(sr);
        }
        catch (DocumentException e)
        {
        } catch (DocumentException e) {
            LOG.error("解析失败", e);
        }
        return null == document ? null : document.getRootElement();
@@ -54,16 +52,12 @@
    /**
     * 获取element对象的text的值
     * 
     * @param em
     *            节点的对象
     * @param tag
     *            节点的tag
     * @param em  节点的对象
     * @param tag 节点的tag
     * @return 节点
     */
    public static String getText(Element em, String tag)
    {
        if (null == em)
        {
    public static String getText(Element em, String tag) {
        if (null == em) {
            return null;
        }
        Element e = em.element(tag);
@@ -74,16 +68,12 @@
    /**
     * 递归解析xml节点,适用于 多节点数据
     * 
     * @param node
     *            node
     * @param nodeName
     *            nodeName
     * @param node     node
     * @param nodeName nodeName
     * @return List<Map<String, Object>>
     */
    public static List<Map<String, Object>> listNodes(Element node, String nodeName)
    {
        if (null == node)
        {
    public static List<Map<String, Object>> listNodes(Element node, String nodeName) {
        if (null == node) {
            return null;
        }
        // 初始化返回
@@ -93,12 +83,9 @@
        Map<String, Object> map = null;
        // 遍历属性节点
        for (Attribute attribute : list)
        {
            if (nodeName.equals(node.getName()))
            {
                if (null == map)
                {
        for (Attribute attribute : list) {
            if (nodeName.equals(node.getName())) {
                if (null == map) {
                    map = new HashMap<String, Object>();
                    listMap.add(map);
                }
@@ -110,12 +97,74 @@
        // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称
        // 使用递归
        Iterator<Element> iterator = node.elementIterator();
        while (iterator.hasNext())
        {
        while (iterator.hasNext()) {
            Element e = iterator.next();
            listMap.addAll(listNodes(e, nodeName));
        }
        return listMap;
    }
    /**
     * xml转json
     *
     * @param element
     * @param json
     */
    public static void node2Json(Element element, JSONObject json) {
        // 如果是属性
        for (Object o : element.attributes()) {
            Attribute attr = (Attribute) o;
            if (!isEmpty(attr.getValue())) {
                json.put("@" + attr.getName(), attr.getValue());
            }
        }
        List<Element> chdEl = element.elements();
        if (chdEl.isEmpty() && !isEmpty(element.getText())) {// 如果没有子元素,只有一个值
            json.put(element.getName(), element.getText());
        }
        for (Element e : chdEl) {   // 有子元素
            if (!e.elements().isEmpty()) {  // 子元素也有子元素
                JSONObject chdjson = new JSONObject();
                node2Json(e, chdjson);
                Object o = json.get(e.getName());
                if (o != null) {
                    JSONArray jsona = null;
                    if (o instanceof JSONObject) {  // 如果此元素已存在,则转为jsonArray
                        JSONObject jsono = (JSONObject) o;
                        json.remove(e.getName());
                        jsona = new JSONArray();
                        jsona.add(jsono);
                        jsona.add(chdjson);
                    }
                    if (o instanceof JSONArray) {
                        jsona = (JSONArray) o;
                        jsona.add(chdjson);
                    }
                    json.put(e.getName(), jsona);
                } else {
                    if (!chdjson.isEmpty()) {
                        json.put(e.getName(), chdjson);
                    }
                }
            } else { // 子元素没有子元素
                for (Object o : element.attributes()) {
                    Attribute attr = (Attribute) o;
                    if (!isEmpty(attr.getValue())) {
                        json.put("@" + attr.getName(), attr.getValue());
                    }
                }
                if (!e.getText().isEmpty()) {
                    json.put(e.getName(), e.getText());
                }
            }
        }
    }
    public static boolean isEmpty(String str) {
        if (str == null || str.trim().isEmpty() || "null".equals(str)) {
            return true;
        }
        return false;
    }
}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
@@ -1,34 +1,28 @@
package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
@RestController
@RequestMapping("/zlm")
public class ZLMHTTPProxyController {
    private final static Logger logger = LoggerFactory.getLogger(ZLMHTTPProxyController.class);
    // private final static Logger logger = LoggerFactory.getLogger(ZLMHTTPProxyController.class);
    @Autowired
    private IVideoManagerStorager storager;
    // @Autowired
    // private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -82,10 +82,10 @@
    @ResponseBody
    @PostMapping(value = "/on_flow_report", produces = "application/json;charset=UTF-8")
    public ResponseEntity<String> onFlowReport(@RequestBody JSONObject json){
        logger.debug("ZLM HOOK on_flow_report API调用,参数:" + json.toString());
        // TODO Auto-generated method stub
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_flow_report API调用,参数:" + json.toString());
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -103,8 +103,6 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_http_access API 调用,参数:" + json.toString());
        }
        // TODO Auto-generated method stub
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("err", "");
@@ -124,8 +122,6 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_play API调用,参数:" + json.toString());
        }
        // TODO Auto-generated method stub
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -143,15 +139,10 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_publish API调用,参数:" + json.toString());
        }
        String app = json.getString("app");
        String streamId = json.getString("id");
        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
        if (subscribe != null) subscribe.response(json);
        // TODO Auto-generated method stub
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -172,8 +163,6 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_record_mp4 API调用,参数:" + json.toString());
        }
        // TODO Auto-generated method stub
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -191,8 +180,6 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_rtsp_realm API调用,参数:" + json.toString());
        }
        // TODO Auto-generated method stub
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("realm", "");
@@ -211,8 +198,6 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_rtsp_auth API调用,参数:" + json.toString());
        }
        // TODO Auto-generated method stub
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("encrypted", false);
@@ -269,8 +254,6 @@
                redisCatchStorage.stopPlayback(streamInfo);
            }
        }
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
@@ -318,8 +301,6 @@
        if (logger.isDebugEnabled()) {
            logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
        }
        // TODO Auto-generated method stub
        if (autoApplyPlay) {
            String app = json.getString("app");
            String streamId = json.getString("stream");
@@ -369,8 +350,6 @@
        mediaServerConfig.setWanIp(StringUtils.isEmpty(mediaWanIp)? mediaIp: mediaWanIp);
        mediaServerConfig.setLocalIP(mediaIp);
        redisCatchStorage.updateMediaInfo(mediaServerConfig);
        // TODO Auto-generated method stub
        JSONObject ret = new JSONObject();
        ret.put("code", 0);
        ret.put("msg", "success");
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java
@@ -1,24 +1,8 @@
package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -30,8 +14,6 @@
 */
@Component
public class ZLMHttpHookSubscribe {
    private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookSubscribe.class);
    public enum HookType{
        on_flow_report,
@@ -72,8 +54,6 @@
        for (JSONObject key : eventMap.keySet()) {
            Boolean result = null;
            for (String s : key.keySet()) {
                String string = hookResponse.getString(s);
                String string1 = key.getString(s);
                if (result == null) {
                    result = key.getString(s).equals(hookResponse.getString(s));
                }else {
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
@@ -11,7 +11,6 @@
import java.io.IOException;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@Component
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -5,8 +5,7 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import okhttp3.*;
//import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -16,9 +15,6 @@
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@@ -28,8 +24,8 @@
    private final static Logger logger = LoggerFactory.getLogger(ZLMRunner.class);
    @Autowired
    private IVideoManagerStorager storager;
    // @Autowired
    // private IVideoManagerStorager storager;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -8,6 +8,7 @@
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.github.pagehelper.PageInfo;
import gov.nist.javax.sip.stack.NioTcpMessageProcessor;
@@ -236,4 +237,32 @@
    DeviceChannel queryChannelInParentPlatform(String platformId, String channelId);
    Device queryVideoDeviceByPlatformIdAndChannelId(String platformId, String channelId);
    /**
     * 添加Mobile Position设备移动位置
     * @param MobilePosition
     * @return
     */
    public boolean insertMobilePosition(MobilePosition mobilePosition);
    /**
     * 查询移动位置轨迹
     * @param deviceId
     * @param startTime
     * @param endTime
     */
    public List<MobilePosition> queryMobilePositions(String deviceId, String startTime, String endTime);
    /**
     * 查询最新移动位置
     * @param deviceId
     */
    public MobilePosition queryLatestPosition(String deviceId);
    /**
     * 删除指定设备的所有移动位置
     * @param deviceId
     */
    public int clearMobilePositionsByDeviceId(String deviceId);
}
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMobilePositionMapper.java
New file
@@ -0,0 +1,33 @@
package com.genersoft.iot.vmp.storager.dao;
import java.util.List;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import org.apache.ibatis.annotations.*;
//import org.springframework.stereotype.Repository;
@Mapper
//@Repository
public interface DeviceMobilePositionMapper {
    @Insert("INSERT INTO device_mobile_position (deviceId, deviceName, time, longitude, latitude, altitude, speed, direction, reportSource, geodeticSystem, cnLng, cnLat) " +
            "VALUES ('${deviceId}', '${deviceName}', '${time}', ${longitude}, ${latitude}, ${altitude}, ${speed}, ${direction}, '${reportSource}', '${geodeticSystem}', '${cnLng}', '${cnLat}')")
    int insertNewPosition(MobilePosition mobilePosition);
    @Select(value = {" <script>" +
    "SELECT * FROM device_mobile_position" +
    " WHERE deviceId = #{deviceId} " +
    "<if test=\"startTime != null\"> AND time&gt;=#{startTime}</if>" +
    "<if test=\"endTime != null\"> AND time&lt;=#{endTime}</if>" +
    " ORDER BY time ASC" +
    " </script>"})
    List<MobilePosition> queryPositionByDeviceIdAndTime(String deviceId, String startTime, String endTime);
    @Select("SELECT * FROM device_mobile_position WHERE deviceId = #{deviceId}" +
            " ORDER BY time DESC LIMIT 1")
    MobilePosition queryLatestPositionByDevice(String deviceId);
    @Delete("DELETE FROM device_mobile_position WHERE deviceId = #{deviceId}")
    int clearMobilePositionsByDeviceId(String deviceId);
}
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -14,7 +14,6 @@
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -147,9 +146,9 @@
    @Override
    public StreamInfo queryPlaybackByDevice(String deviceId, String code) {
        String format = String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
                deviceId,
                code);
        // String format = String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
        //         deviceId,
        //         code);
        List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
                deviceId,
                code));
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
@@ -6,11 +6,13 @@
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
import com.genersoft.iot.vmp.storager.dao.PatformChannelMapper;
import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
import com.genersoft.iot.vmp.storager.dao.DeviceMobilePositionMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,7 +34,10 @@
    private DeviceMapper deviceMapper;
    @Autowired
    private DeviceChannelMapper deviceChannelMapper;
    private DeviceChannelMapper deviceChannelMapper;
    @Autowired
    private DeviceMobilePositionMapper deviceMobilePositionMapper;
    @Autowired
    private ParentPlatformMapper platformMapper;
@@ -194,11 +199,11 @@
    @Override
    public synchronized boolean online(String deviceId) {
        Device device = deviceMapper.getDeviceByDeviceId(deviceId);
        device.setOnline(1);
        System.out.println("更新设备在线");
        if (device == null) {
            return false;
        }
        device.setOnline(1);
        System.out.println("更新设备在线");
        return deviceMapper.update(device) > 0;
    }
@@ -216,10 +221,33 @@
        return deviceMapper.update(device) > 0;
    }
    /**
     * 清空通道
     * @param deviceId
     */
    @Override
    public void cleanChannelsForDevice(String deviceId) {
        int result = deviceChannelMapper.cleanChannelsByDeviceId(deviceId);
        deviceChannelMapper.cleanChannelsByDeviceId(deviceId);
    }
    /**
     * 添加Mobile Position设备移动位置
     * @param MobilePosition
     */
    @Override
    public synchronized boolean insertMobilePosition(MobilePosition mobilePosition) {
        return deviceMobilePositionMapper.insertNewPosition(mobilePosition) > 0;
    }
    /**
     * 查询移动位置轨迹
     * @param deviceId
     * @param startTime
     * @param endTime
     */
    @Override
    public synchronized List<MobilePosition> queryMobilePositions(String deviceId, String startTime, String endTime) {
        return deviceMobilePositionMapper.queryPositionByDeviceIdAndTime(deviceId, startTime, endTime);
    }
    @Override
@@ -283,7 +311,7 @@
    @Override
    public PageInfo<ChannelReduce> queryAllChannelList(int page, int count, String query, Boolean online,
                                                         Boolean channelType, String platformId, Boolean inPlatform) {
                                                       Boolean channelType, String platformId, Boolean inPlatform) {
        PageHelper.startPage(page, count);
        List<ChannelReduce> all = deviceChannelMapper.queryChannelListInAll(query, online, channelType, platformId, inPlatform);
        return new PageInfo<>(all);
@@ -341,4 +369,22 @@
        Device device = patformChannelMapper.queryVideoDeviceByPlatformIdAndChannelId(platformId, channelId);
        return device;
    }
    /**
     * 查询最新移动位置
     * @param deviceId
     */
    @Override
    public MobilePosition queryLatestPosition(String deviceId) {
        return deviceMobilePositionMapper.queryLatestPositionByDevice(deviceId);
    }
    /**
     * 删除指定设备的所有移动位置
     * @param deviceId
     */
    public int clearMobilePositionsByDeviceId(String deviceId) {
        return deviceMobilePositionMapper.clearMobilePositionsByDeviceId(deviceId);
    }
}
src/main/java/com/genersoft/iot/vmp/utils/GpsUtil.java
New file
@@ -0,0 +1,68 @@
package com.genersoft.iot.vmp.utils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Base64;
import com.genersoft.iot.vmp.gb28181.bean.BaiduPoint;
public class GpsUtil {
    public static BaiduPoint Wgs84ToBd09(String xx, String yy) {
        try {
            Socket s = new Socket("api.map.baidu.com", 80);
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
            OutputStream out = s.getOutputStream();
            StringBuffer sb = new StringBuffer("GET /ag/coord/convert?from=0&to=4");
            sb.append("&x=" + xx + "&y=" + yy);
            sb.append("&callback=BMap.Convertor.cbk_3976 HTTP/1.1\r\n");
            sb.append("User-Agent: Java/1.6.0_20\r\n");
            sb.append("Host: api.map.baidu.com:80\r\n");
            sb.append("Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n");
            sb.append("Connection: Close\r\n");
            sb.append("\r\n");
            out.write(sb.toString().getBytes());
            String json = "";
            String tmp = "";
            while ((tmp = br.readLine()) != null) {
                // System.out.println(tmp);
                json += tmp;
            }
            s.close();
            int start = json.indexOf("cbk_3976");
            int end = json.lastIndexOf("}");
            if (start != -1 && end != -1 && json.contains("\"x\":\"")) {
                json = json.substring(start, end);
                String[] point = json.split(",");
                String x = point[1].split(":")[1].replace("\"", "");
                String y = point[2].split(":")[1].replace("\"", "");
                BaiduPoint bdPoint= new BaiduPoint();
                bdPoint.setBdLng(new String(decode(x)));
                bdPoint.setBdLat(new String(decode(y)));
                return bdPoint;
                //return (new String(decode(x)) + "," + new String(decode(y)));
            } else {
                System.out.println("gps坐标无效!!");
            }
            out.close();
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * BASE64解码
     * @param str
     * @return string
     */
    public static byte[] decode(String str) {
        byte[] bt = null;
        final Base64.Decoder decoder = Base64.getDecoder();
        bt = decoder.decode(str); // .decodeBuffer(str);
        return bt;
    }
}
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
@@ -4,8 +4,6 @@
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@@ -16,6 +14,7 @@
 * @date:   2020年5月6日 下午8:27:29     
 */
@Component
@SuppressWarnings(value = {"rawtypes", "unchecked"})
public class RedisUtil {
    @Autowired
src/main/java/com/genersoft/iot/vmp/vmanager/MobilePosition/MobilePositionController.java
New file
@@ -0,0 +1,118 @@
package com.genersoft.iot.vmp.vmanager.MobilePosition;
import java.util.List;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.github.pagehelper.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class MobilePositionController {
    private final static Logger logger = LoggerFactory.getLogger(MobilePositionController.class);
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private SIPCommander cmder;
    @Autowired
    private DeferredResultHolder resultHolder;
    @GetMapping("/positions/{deviceId}/history")
    public ResponseEntity<List<MobilePosition>> positions(@PathVariable String deviceId,
                                                    @RequestParam(required = false) String start,
                                                    @RequestParam(required = false) String end) {
        if (logger.isDebugEnabled()) {
            logger.debug("查询设备" + deviceId + "的历史轨迹");
        }
        if (StringUtil.isEmpty(start)) {
            start = null;
        }
        if (StringUtil.isEmpty(end)) {
            end = null;
        }
        List<MobilePosition> result = storager.queryMobilePositions(deviceId, start, end);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }
    @GetMapping("/positions/{deviceId}/latest")
    public ResponseEntity<MobilePosition> latestPosition(@PathVariable String deviceId) {
        if (logger.isDebugEnabled()) {
            logger.debug("查询设备" + deviceId + "的最新位置");
        }
        MobilePosition result = storager.queryLatestPosition(deviceId);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }
    @GetMapping("/positions/{deviceId}/realtime")
    public DeferredResult<ResponseEntity<MobilePosition>> realTimePosition(@PathVariable String deviceId) {
        Device device = storager.queryVideoDevice(deviceId);
        cmder.mobilePostitionQuery(device, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_MOBILEPOSITION + deviceId);
            msg.setData(String.format("获取移动位置信息失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<MobilePosition>> result = new DeferredResult<ResponseEntity<MobilePosition>>(5*1000L);
        result.onTimeout(()->{
            logger.warn(String.format("获取移动位置信息超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId);
            msg.setData("Timeout");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
        return result;
    }
    @GetMapping("/positions/{deviceId}/subscribe")
    public ResponseEntity<String> positionSubscribe(@PathVariable String deviceId,
                                                    @RequestParam String expires,
                                                    @RequestParam String interval) {
        String msg = ((expires.equals("0")) ? "取消" : "") + "订阅设备" + deviceId + "的移动位置";
        if (logger.isDebugEnabled()) {
            logger.debug(msg);
        }
        if (StringUtil.isEmpty(interval)) {
            interval = "5";
        }
        Device device = storager.queryVideoDevice(deviceId);
        String result = msg;
        if (cmder.mobilePositionSubscribe(device, Integer.parseInt(expires), Integer.parseInt(interval))) {
            result += ",成功";
        } else {
            result += ",失败";
        }
        return new ResponseEntity<>(result, HttpStatus.OK);
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/SseController/SseController.java
@@ -3,6 +3,7 @@
import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEventListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -13,6 +14,7 @@
 * @data: 2021-01-20
 */
@CrossOrigin
@Controller
@RequestMapping("/api")
public class SseController {
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfig.java
New file
@@ -0,0 +1,120 @@
/**
 * 设备设置命令API接口
 *
 * @author lawrencehj
 * @date 2021年2月2日
 */
package com.genersoft.iot.vmp.vmanager.device;
import javax.sip.message.Response;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceConfig {
    private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private SIPCommander cmder;
    @Autowired
    private DeferredResultHolder resultHolder;
    /**
     * 看守位控制命令API接口
     *
     * @param deviceId
     * @param enabled       看守位使能1:开启,0:关闭
     * @param resetTime     自动归位时间间隔(可选)
     * @param presetIndex   调用预置位编号(可选)
     * @param channelId     通道编码(可选)
     */
    @GetMapping("/config/{deviceId}/basicParam")
    public DeferredResult<ResponseEntity<String>> homePositionApi(@PathVariable String deviceId,
                                                                @RequestParam(required = false) String channelId,
                                                                @RequestParam(required = false) String name,
                                                                @RequestParam(required = false) String expiration,
                                                                @RequestParam(required = false) String heartBeatInterval,
                                                                @RequestParam(required = false) String heartBeatCount) {
        if (logger.isDebugEnabled()) {
            logger.debug("报警复位API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.deviceBasicConfigCmd(device, channelId, name, expiration, heartBeatInterval, heartBeatCount, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("设备配置操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
        result.onTimeout(() -> {
            logger.warn(String.format("设备配置操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("Status", "Timeout");
            json.put("Description", "设备配置操作超时, 设备未返回应答指令");
            msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
    /**
     * 设备配置查询请求API接口
     *
     * @param deviceId
     */
    @GetMapping("/config/{deviceId}/query/{configType}")
    public DeferredResult<ResponseEntity<String>> configDownloadApi(@PathVariable String deviceId,
                                                                @PathVariable String configType,
                                                                @RequestParam(required = false) String channelId) {
        if (logger.isDebugEnabled()) {
            logger.debug("设备状态查询API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.deviceConfigQuery(device, channelId, configType, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("获取设备配置失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
        result.onTimeout(()->{
            logger.warn(String.format("获取设备配置超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData("Timeout. Device did not response to this command.");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControl.java
New file
@@ -0,0 +1,238 @@
/**
 * 设备控制命令API接口
 *
 * @author lawrencehj
 * @date 2021年2月1日
 */
package com.genersoft.iot.vmp.vmanager.device;
import javax.sip.message.Response;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceControl {
    private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private SIPCommander cmder;
    @Autowired
    private DeferredResultHolder resultHolder;
    /**
     * 远程启动控制命令API接口
     *
     * @param deviceId
     */
    @GetMapping("/control/{deviceId}/teleboot")
    @PostMapping("/control/{deviceId}/teleboot")
    public ResponseEntity<String> teleBootApi(@PathVariable String deviceId) {
        if (logger.isDebugEnabled()) {
            logger.debug("设备远程启动API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        boolean sucsess = cmder.teleBootCmd(device);
        if (sucsess) {
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("Result", "OK");
            return new ResponseEntity<>(json.toJSONString(), HttpStatus.OK);
        } else {
            logger.warn("设备远程启动API调用失败!");
            return new ResponseEntity<String>("设备远程启动API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    /**
     * 录像控制命令API接口
     *
     * @param deviceId
     * @param recordCmdStr  Record:手动录像,StopRecord:停止手动录像
     * @param channelId     通道编码(可选)
     */
    @GetMapping("/control/{deviceId}/record/{recordCmdStr}")
    public DeferredResult<ResponseEntity<String>> recordApi(@PathVariable String deviceId,
            @PathVariable String recordCmdStr, @RequestParam(required = false) String channelId) {
        if (logger.isDebugEnabled()) {
            logger.debug("开始/停止录像API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.recordCmd(device, channelId, recordCmdStr, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
        result.onTimeout(() -> {
            logger.warn(String.format("开始/停止录像操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData("Timeout. Device did not response to this command.");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
    /**
     * 报警布防/撤防命令API接口
     *
     * @param    deviceId
     * @param    guardCmdStr SetGuard:布防,ResetGuard:撤防
     */
    @GetMapping("/control/{deviceId}/guard/{guardCmdStr}")
    public DeferredResult<ResponseEntity<String>> guardApi(@PathVariable String deviceId, @PathVariable String guardCmdStr) {
        if (logger.isDebugEnabled()) {
            logger.debug("布防/撤防API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.guardCmd(device, guardCmdStr, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
            msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
        result.onTimeout(() -> {
            logger.warn(String.format("布防/撤防操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
            msg.setData("Timeout. Device did not response to this command.");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId, result);
        return result;
    }
    /**
     * 报警复位API接口
     *
     * @param    deviceId
     * @param    alarmMethod 报警方式(可选)
     * @param    alarmType   报警类型(可选)
     */
    @GetMapping("/control/{deviceId}/resetAlarm")
    public DeferredResult<ResponseEntity<String>> resetAlarmApi(@PathVariable String deviceId,
                                                                @RequestParam(required = false) String alarmMethod,
                                                                @RequestParam(required = false) String alarmType) {
        if (logger.isDebugEnabled()) {
            logger.debug("报警复位API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.alarmCmd(device, alarmMethod, alarmType, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
            msg.setData(String.format("报警复位操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
        result.onTimeout(() -> {
            logger.warn(String.format("报警复位操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
            msg.setData("Timeout. Device did not response to this command.");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId, result);
        return result;
    }
    /**
     * 强制关键帧API接口
     *
     * @param    deviceId
     * @param    channelId
     */
    @GetMapping("/control/{deviceId}/iFrame")
    @PostMapping("/control/{deviceId}/iFrame")
    public ResponseEntity<String> iFrame(@PathVariable String deviceId,
                                        @RequestParam(required = false) String channelId) {
        if (logger.isDebugEnabled()) {
            logger.debug("强制关键帧API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        boolean sucsess = cmder.iFrameCmd(device, channelId);
        if (sucsess) {
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("ChannelID", channelId);
            json.put("Result", "OK");
            return new ResponseEntity<>(json.toJSONString(), HttpStatus.OK);
        } else {
            logger.warn("强制关键帧API调用失败!");
            return new ResponseEntity<String>("强制关键帧API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    /**
     * 看守位控制命令API接口
     *
     * @param deviceId
     * @param enabled       看守位使能1:开启,0:关闭
     * @param resetTime     自动归位时间间隔(可选)
     * @param presetIndex   调用预置位编号(可选)
     * @param channelId     通道编码(可选)
     */
    @GetMapping("/control/{deviceId}/homePosition/{enabled}")
    public DeferredResult<ResponseEntity<String>> homePositionApi(@PathVariable String deviceId,
                                                                @PathVariable String enabled,
                                                                @RequestParam(required = false) String resetTime,
                                                                @RequestParam(required = false) String presetIndex,
                                                                @RequestParam(required = false) String channelId) {
        if (logger.isDebugEnabled()) {
            logger.debug("报警复位API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
        result.onTimeout(() -> {
            logger.warn(String.format("看守位控制操作超时, 设备未返回应答指令"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("Status", "Timeout");
            json.put("Description", "看守位控制操作超时, 设备未返回应答指令");
            msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceQuery.java
File was renamed from src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
@@ -21,12 +21,13 @@
import javax.sip.message.Response;
@SuppressWarnings("rawtypes")
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceController {
public class DeviceQuery {
    
    private final static Logger logger = LoggerFactory.getLogger(DeviceController.class);
    private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
    
    @Autowired
    private IVideoManagerStorager storager;
@@ -77,11 +78,9 @@
                                               int page, int count,
                                               @RequestParam(required = false) String query,
                                               @RequestParam(required = false) Boolean online,
                                               @RequestParam(required = false) Boolean channelType
    ){
                                               @RequestParam(required = false) Boolean channelType) {
        if (logger.isDebugEnabled()) {
            logger.debug("查询所有视频设备API调用");
            logger.debug("查询视频设备通道API调用");
        }
        if (StringUtils.isEmpty(query)) {
            query = null;
@@ -135,8 +134,8 @@
            json.put("deviceId", deviceId);
            return new ResponseEntity<>(json.toString(),HttpStatus.OK);
        } else {
            logger.warn("设备预览API调用失败!");
            return new ResponseEntity<String>("设备预览API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
            logger.warn("设备信息删除API调用失败!");
            return new ResponseEntity<String>("设备信息删除API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
@@ -157,7 +156,7 @@
                                                  @RequestParam(required = false) Boolean channelType){
        if (logger.isDebugEnabled()) {
            logger.debug("查询所有视频设备API调用");
            logger.debug("查询所有视频通道API调用");
        }
        DeviceChannel deviceChannel = storager.queryChannel(deviceId,channelId);
        if (deviceChannel == null) {
@@ -183,4 +182,74 @@
        storager.updateDevice(device);
        return new ResponseEntity<>(null,HttpStatus.OK);
    }
    /**
     * 设备状态查询请求API接口
     *
     * @param deviceId
     */
    @GetMapping("/devices/{deviceId}/status")
    public DeferredResult<ResponseEntity<String>> deviceStatusApi(@PathVariable String deviceId) {
        if (logger.isDebugEnabled()) {
            logger.debug("设备状态查询API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.deviceStatusQuery(device, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId);
            msg.setData(String.format("获取设备状态失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L);
        result.onTimeout(()->{
            logger.warn(String.format("获取设备状态超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId);
            msg.setData("Timeout. Device did not response to this command.");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId, result);
        return result;
    }
    /**
     * 设备报警查询请求API接口
     *
     * @param deviceId
     */
    @GetMapping("/alarm/{deviceId}")
    public DeferredResult<ResponseEntity<String>> alarmApi(@PathVariable String deviceId,
                                                        @RequestParam(required = false) String startPriority,
                                                        @RequestParam(required = false) String endPriority,
                                                        @RequestParam(required = false) String alarmMethod,
                                                        @RequestParam(required = false) String alarmType,
                                                        @RequestParam(required = false) String startTime,
                                                        @RequestParam(required = false) String endTime) {
        if (logger.isDebugEnabled()) {
            logger.debug("设备报警查询API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.alarmInfoQuery(device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId);
            msg.setData(String.format("设备报警查询失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
        result.onTimeout(()->{
            logger.warn(String.format("设备报警查询超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId);
            msg.setData("设备报警查询超时");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId, result);
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
@@ -1,21 +1,17 @@
package com.genersoft.iot.vmp.vmanager.play;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.vmanager.play.bean.PlayResult;
import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
@@ -32,7 +28,6 @@
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response;
import java.text.DecimalFormat;
import java.util.UUID;
@CrossOrigin
@@ -103,7 +98,7 @@
                storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid);
                Response response = event.getResponse();
                //Response response = event.getResponse();
                msg.setData(String.format("success"));
                resultHolder.invokeResult(msg);
            }
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
@@ -1,24 +1,19 @@
package com.genersoft.iot.vmp.vmanager.playback;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
//import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -47,8 +42,8 @@
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    // @Autowired
    // private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
    private IPlayService playService;
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java
@@ -5,14 +5,16 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@CrossOrigin
@@ -28,6 +30,9 @@
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private DeferredResultHolder resultHolder;
    /***
     * 云台控制
     * @param deviceId 设备id
@@ -49,16 +54,18 @@
        cmder.frontEndCmd(device, channelId, cmdCode, horizonSpeed, verticalSpeed, zoomSpeed);
        return new ResponseEntity<String>("success",HttpStatus.OK);
    }
    // public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){
    //     if (logger.isDebugEnabled()) {
    //         logger.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,leftRight:%d ,upDown:%d ,inOut:%d ,moveSpeed:%d ,zoomSpeed:%d",deviceId, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed));
    //     }
    //     Device device = storager.queryVideoDevice(deviceId);
    //     cmder.ptzCmd(device, channelId, leftRight, upDown, inOut, moveSpeed, zoomSpeed);
    //     return new ResponseEntity<String>("success",HttpStatus.OK);
    // }
    /**
     * 通用前端控制命令API接口
     *
     * @param deviceId
     * @param channelId
     * @param cmdCode
     * @param parameter1
     * @param parameter2
     * @param combindCode2
     * @return
     */
    @PostMapping("/frontEndCommand/{deviceId}/{channelId}")
    public ResponseEntity<String> frontEndCommand(@PathVariable String deviceId,@PathVariable String channelId,int cmdCode, int parameter1, int parameter2, int combindCode2){
        
@@ -70,4 +77,37 @@
        cmder.frontEndCmd(device, channelId, cmdCode, parameter1, parameter2, combindCode2);
        return new ResponseEntity<String>("success",HttpStatus.OK);
    }
    /**
     * 预置位查询命令API接口
     *
     * @param deviceId
     * @param channelId
     * @return
     */
    @GetMapping("/presetQuery/{deviceId}/{channelId}")
    public DeferredResult<ResponseEntity<String>> presetQueryApi(@PathVariable String deviceId, @PathVariable String channelId) {
        if (logger.isDebugEnabled()) {
            logger.debug("设备预置位查询API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        cmder.presetQuery(device, channelId, event -> {
            Response response = event.getResponse();
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData(String.format("获取设备预置位失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
            resultHolder.invokeResult(msg);
        });
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
        result.onTimeout(()->{
            logger.warn(String.format("获取设备预置位超时"));
            // 释放rtpserver
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
            msg.setData("获取设备预置位超时");
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
        return result;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java
@@ -1,12 +1,9 @@
package com.genersoft.iot.vmp.vmanager.service;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.vmanager.play.bean.PlayResult;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
/**
 * 点播处理
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
@@ -1,8 +1,5 @@
package com.genersoft.iot.vmp.vmanager.user;
import com.genersoft.iot.vmp.vmanager.play.PlayController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
@@ -13,9 +10,6 @@
@RestController
@RequestMapping("/api")
public class UserController {
    private final static Logger logger = LoggerFactory.getLogger(UserController.class);
    @Value("${auth.username}")
    private String usernameConfig;
src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java
@@ -4,12 +4,9 @@
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.ptz.PtzController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
src/main/java/com/genersoft/iot/vmp/web/ApiController.java
@@ -5,11 +5,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java
@@ -4,9 +4,9 @@
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
// import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
// import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
// import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
@@ -19,6 +19,7 @@
/**
 * 兼容LiveGBS的API:设备信息
 */
@SuppressWarnings("unchecked")
@CrossOrigin
@RestController
@RequestMapping(value = "/api/v1/device")
@@ -29,14 +30,14 @@
    @Autowired
    private IVideoManagerStorager storager;
    @Autowired
    private SIPCommander cmder;
    // @Autowired
    // private SIPCommander cmder;
    @Autowired
    private DeferredResultHolder resultHolder;
    // @Autowired
    // private DeferredResultHolder resultHolder;
    @Autowired
    private DeviceOffLineDetector offLineDetector;
    // @Autowired
    // private DeviceOffLineDetector offLineDetector;
    /**
     * 分页获取设备列表 TODO 现在直接返回,尚未实现分页
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
@@ -1,21 +1,18 @@
package com.genersoft.iot.vmp.web;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
// import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.play.PlayController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@@ -23,6 +20,7 @@
/**
 * 兼容LiveGBS的API:实时直播
 */
@SuppressWarnings(value = {"rawtypes", "unchecked"})
@CrossOrigin
@RestController
@RequestMapping(value = "/api/v1/stream")
@@ -40,8 +38,8 @@
    private IRedisCatchStorage redisCatchStorage;
    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;
    // @Autowired
    // private ZLMRESTfulUtils zlmresTfulUtils;
    @Autowired
src/main/java/com/genersoft/iot/vmp/web/AuthController.java
@@ -1,9 +1,6 @@
package com.genersoft.iot.vmp.web;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
src/main/resources/application-dev.yml
@@ -92,4 +92,8 @@
    level:
        com:
            genersoft:
                iot: debug
                iot: debug
# [根据业务需求配置]
userSettings:
    # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认)
    savePositionHistory: false
web_src/.postcssrc.js
@@ -7,7 +7,7 @@
    // to edit target browsers: use "browserslist" field in package.json
    "autoprefixer": {},
    'postcss-pxtorem': {
      rootValue: 16,
      rootValue: 24,
      propList: ['font-size'] // 只转化font-size
    }
  }
web_src/index.html
@@ -7,6 +7,7 @@
  </head>
  <body>
    <script type="text/javascript" src="./js/EasyWasmPlayer.js"></script>
    <script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak=rk73w8dv1rkE4UdZsataG68VarhYQzrx&s=1"></script>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
web_src/package-lock.json
@@ -1269,6 +1269,34 @@
      "integrity": "sha1-nyKcFb4nJFT/qXOs4NvueaGww28=",
      "dev": true
    },
    "bmaplib.curveline": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/bmaplib.curveline/-/bmaplib.curveline-1.0.0.tgz",
      "integrity": "sha512-9wcFMVhiYxNPqpvsLDAADn3qDhNzXp2mA6VyHSHg2XOAgSooC7ZiujdFhy0sp+0QYjTfJ/MjmLuNoUg2HHxH4Q=="
    },
    "bmaplib.heatmap": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/bmaplib.heatmap/-/bmaplib.heatmap-1.0.4.tgz",
      "integrity": "sha512-rmhqUARBpUSJ9jXzUI2j7dIOqnc38bqubkx/8a349U2qtw/ulLUwyzRD535OrA8G7w5cz4aPKm6/rNvUAarg/Q=="
    },
    "bmaplib.lushu": {
      "version": "1.0.7",
      "resolved": "https://registry.npmjs.org/bmaplib.lushu/-/bmaplib.lushu-1.0.7.tgz",
      "integrity": "sha512-LVvgpESPii6xGxyjnQjq8u+ic4NjvhdCPV/RiSS/PGTUdZKeTDS7prSpleJLZH3ES0+oc0gYn8bw0LtPYUSz2w=="
    },
    "bmaplib.markerclusterer": {
      "version": "1.0.13",
      "resolved": "https://registry.npmjs.org/bmaplib.markerclusterer/-/bmaplib.markerclusterer-1.0.13.tgz",
      "integrity": "sha512-VrLyWSiuDEVNi0yUfwOhFQ6z1oEEHS4w36GNu3iASu6p52QIx9uAXMUkuSCHReNR0bj2Cp9SA1dSx5RpojXajQ==",
      "requires": {
        "bmaplib.texticonoverlay": "^1.0.2"
      }
    },
    "bmaplib.texticonoverlay": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/bmaplib.texticonoverlay/-/bmaplib.texticonoverlay-1.0.2.tgz",
      "integrity": "sha512-4ZTWr4ZP3B6qEWput5Tut16CfZgII38YwM3bpyb4gFTQyORlKYryFp9WHWrwZZaHlOyYDAXG9SX0hka43jTADg=="
    },
    "bn.js": {
      "version": "5.1.3",
      "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-5.1.3.tgz",
@@ -5266,6 +5294,14 @@
        "invert-kv": "^1.0.0"
      }
    },
    "linkify-it": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
      "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
      "requires": {
        "uc.micro": "^1.0.1"
      }
    },
    "load-json-file": {
      "version": "2.0.0",
      "resolved": "https://registry.npm.taobao.org/load-json-file/download/load-json-file-2.0.0.tgz",
@@ -5443,6 +5479,25 @@
        "object-visit": "^1.0.0"
      }
    },
    "markdown-it": {
      "version": "8.4.2",
      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz",
      "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==",
      "requires": {
        "argparse": "^1.0.7",
        "entities": "~1.1.1",
        "linkify-it": "^2.0.0",
        "mdurl": "^1.0.1",
        "uc.micro": "^1.0.5"
      },
      "dependencies": {
        "entities": {
          "version": "1.1.2",
          "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
          "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
        }
      }
    },
    "math-expression-evaluator": {
      "version": "1.2.22",
      "resolved": "https://registry.npm.taobao.org/math-expression-evaluator/download/math-expression-evaluator-1.2.22.tgz",
@@ -5465,6 +5520,11 @@
      "resolved": "https://registry.npm.taobao.org/mdn-data/download/mdn-data-2.0.4.tgz",
      "integrity": "sha1-aZs8OKxvHXKAkaZGULZdOIUC/Vs=",
      "dev": true
    },
    "mdurl": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
      "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
    },
    "media-typer": {
      "version": "0.3.0",
@@ -10074,8 +10134,7 @@
    "sprintf-js": {
      "version": "1.0.3",
      "resolved": "https://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz",
      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
      "dev": true
      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
    },
    "ssri": {
      "version": "5.3.0",
@@ -10489,6 +10548,11 @@
      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
      "dev": true
    },
    "uc.micro": {
      "version": "1.0.6",
      "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
      "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
    },
    "uglify-js": {
      "version": "3.4.10",
      "resolved": "https://registry.npm.taobao.org/uglify-js/download/uglify-js-3.4.10.tgz?cache=0&sync_timestamp=1601823880483&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuglify-js%2Fdownload%2Fuglify-js-3.4.10.tgz",
@@ -10841,6 +10905,18 @@
      "resolved": "https://registry.npm.taobao.org/vue/download/vue-2.6.12.tgz?cache=0&sync_timestamp=1600441238751&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-2.6.12.tgz",
      "integrity": "sha1-9evU+mvShpQD4pqJau1JBEVskSM="
    },
    "vue-baidu-map": {
      "version": "0.21.22",
      "resolved": "https://registry.npmjs.org/vue-baidu-map/-/vue-baidu-map-0.21.22.tgz",
      "integrity": "sha512-WQMPCih4UTh0AZCKKH/OVOYnyAWjfRNeK6BIeoLmscyY5aF8zzlJhz/NOHLb3mdztIpB0Z6aohn4Jd9mfCSjQw==",
      "requires": {
        "bmaplib.curveline": "^1.0.0",
        "bmaplib.heatmap": "^1.0.4",
        "bmaplib.lushu": "^1.0.7",
        "bmaplib.markerclusterer": "^1.0.13",
        "markdown-it": "^8.4.0"
      }
    },
    "vue-clipboard2": {
      "version": "0.3.1",
      "resolved": "https://registry.npm.taobao.org/vue-clipboard2/download/vue-clipboard2-0.3.1.tgz",
web_src/package.json
@@ -19,6 +19,7 @@
    "moment": "^2.29.1",
    "postcss-pxtorem": "^5.1.1",
    "vue": "^2.6.11",
    "vue-baidu-map": "^0.21.22",
    "vue-clipboard2": "^0.3.1",
    "vue-cookies": "^1.7.4",
    "vue-router": "^3.1.6"
web_src/src/components/GeoConvertTools.js
New file
@@ -0,0 +1,250 @@
/**
 * 经纬度转换
 */
export default {
  PI: 3.1415926535897932384626,
  //PI: 3.14159265358979324,
  x_pi: (3.1415926535897932384626 * 3000.0) / 180.0,
  delta: function (lat, lng) {
    // Krasovsky 1940
    //
    // a = 6378245.0, 1/f = 298.3
    // b = a * (1 - f)
    // ee = (a^2 - b^2) / a^2;
    var a = 6378245.0; //  a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
    var ee = 0.00669342162296594323; //  ee: 椭球的偏心率。
    var dLat = this.transformLat(lng - 105.0, lat - 35.0);
    var dLng = this.transformLng(lng - 105.0, lat - 35.0);
    var radLat = (lat / 180.0) * this.PI;
    var magic = Math.sin(radLat);
    magic = 1 - ee * magic * magic;
    var sqrtMagic = Math.sqrt(magic);
    dLat = (dLat * 180.0) / (((a * (1 - ee)) / (magic * sqrtMagic)) * this.PI);
    dLng = (dLng * 180.0) / ((a / sqrtMagic) * Math.cos(radLat) * this.PI);
    return {
      lat: dLat,
      lng: dLng
    };
  },
  /**
   * WGS-84 to GCJ-02 GPS坐标转中国坐标
   * @param  {number} wgsLat GPS纬度
   * @param  {number} wgsLng GPS经度
   * @return {object}        返回中国坐标经纬度对象
   */
  GPSToChina: function (wgsLat, wgsLng) {
    if (this.outOfChina(wgsLat, wgsLng)) return {
      lat: wgsLat,
      lng: wgsLng
    };
    var d = this.delta(wgsLat, wgsLng);
    return {
      lat: Number(wgsLat) + Number(d.lat),
      lng: Number(wgsLng) + Number(d.lng)
    };
  },
  /**
   * GCJ-02 to WGS-84 中国标准坐标转GPS坐标
   * @param  {number} gcjLat 中国标准坐标纬度
   * @param  {number} gcjLng 中国标准坐标经度
   * @return {object}        返回GPS经纬度对象
   */
  chinaToGPS: function (gcjLat, gcjLng) {
    if (this.outOfChina(gcjLat, gcjLng)) return {
      lat: gcjLat,
      lng: gcjLng
    };
    var d = this.delta(gcjLat, gcjLng);
    return {
      lat: Number(gcjLat) - Number(d.lat),
      lng: Number(gcjLng) - Number(d.lng)
    };
  },
  /**
   * GCJ-02 to WGS-84 exactly 中国标准坐标转GPS坐标(精确)
   * @param  {number} gcjLat  中国标准坐标纬度
   * @param  {number} gcjLng  中国标准坐标经度
   * @return {object}         返回GPS经纬度对象(精确)
   */
  chinaToGPSExact: function (gcjLat, gcjLng) {
    var initDelta = 0.01;
    var threshold = 0.000000001;
    var dLat = initDelta,
      dLng = initDelta;
    var mLat = gcjLat - dLat,
      mLng = gcjLng - dLng;
    var pLat = gcjLat + dLat,
      pLng = gcjLng + dLng;
    var wgsLat,
      wgsLng,
      i = 0;
    while (1) {
      wgsLat = (mLat + pLat) / 2;
      wgsLng = (mLng + pLng) / 2;
      var tmp = this.gcj_encrypt(wgsLat, wgsLng);
      dLat = tmp.lat - gcjLat;
      dLng = tmp.lng - gcjLng;
      if (Math.abs(dLat) < threshold && Math.abs(dLng) < threshold) break;
      if (dLat > 0) pLat = wgsLat;
      else mLat = wgsLat;
      if (dLng > 0) pLng = wgsLng;
      else mLng = wgsLng;
      if (++i > 10000) break;
    }
    //console.log(i);
    return {
      lat: wgsLat,
      lng: wgsLng
    };
  },
  /**
   * GCJ-02 to BD-09 中国标准坐标转百度坐标(精确)
   * @param  {number} gcjLat  中国标准坐标纬度
   * @param  {number} gcjLng  中国标准坐标经度
   * @return {object}         返回百度经纬度对象
   */
  chinaToBaidu: function (gcjLat, gcjLng) {
    var x = gcjLng,
      y = gcjLat;
    var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * this.x_pi);
    var theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * this.x_pi);
    var bdLng = z * Math.cos(theta) + 0.0065;
    var bdLat = z * Math.sin(theta) + 0.006;
    return {
      lat: bdLat,
      lng: bdLng
    };
  },
  /**
   * BD-09 to GCJ-02 百度坐标转中国标准坐标
   * @param  {number} bdLat  百度坐标纬度
   * @param  {number} bdLng  百度坐标经度
   * @return {object}        返回中国标准经纬度对象
   */
  baiduToChina: function (bdLat, bdLng) {
    var x = bdLng - 0.0065,
      y = bdLat - 0.006;
    var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.x_pi);
    var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.x_pi);
    var gcjLng = z * Math.cos(theta);
    var gcjLat = z * Math.sin(theta);
    return {
      lat: gcjLat,
      lng: gcjLng
    };
  },
  /**
   * BD-09 to GCJ-02 百度坐标转gps坐标
   * @param  {number} bdLat  百度坐标纬度
   * @param  {number} bdLng  百度坐标经度
   * @return {object}        返回gps经纬度对象
   */
  baiduToGPS: function (bdLat, bdLng) {
    let china = this.baiduToChina(bdLat, bdLng);
    return this.chinaToGPS(china.lat, china.lng);
  },
  /**
   * WGS-84 to to BD-09 GPS坐标转Baidu坐标
   * @param  {number} gpsLat GPS纬度
   * @param  {number} gpsLng GPS经度
   * @return {object}        返回百度经纬度对象
   */
  GPSToBaidu: function (gpsLat, gpsLng) {
    var china = this.GPSToChina(gpsLat, gpsLng);
    return this.chinaToBaidu(china.lat, china.lng);
  },
  /**
   * WGS-84 to Web mercator GPS坐标转墨卡托坐标
   * @param  {number} wgsLat GPS纬度
   * @param  {number} wgsLng GPS经度
   * @return {object}        返回墨卡托经纬度对象
   */
  GPSToMercator: function (wgsLat, wgsLng) {
    var x = (wgsLng * 20037508.34) / 180;
    var y = Math.log(Math.tan(((90 + wgsLat) * this.PI) / 360)) / (this.PI / 180);
    y = (y * 20037508.34) / 180;
    return {
      lat: y,
      lng: x
    };
    /*
    if ((Math.abs(wgsLng) > 180 || Math.abs(wgsLat) > 90))
        return null;
    var x = 6378137.0 * wgsLng * 0.017453292519943295;
    var a = wgsLat * 0.017453292519943295;
    var y = 3189068.5 * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)));
    return {'lat' : y, 'lng' : x};
    //*/
  },
  /**
   * Web mercator to WGS-84 墨卡托坐标转GPS坐标
   * @param  {number} mercatorLat 墨卡托纬度
   * @param  {number} mercatorLng 墨卡托经度
   * @return {object}             返回GPS经纬度对象
   */
  mercatorToGPS: function (mercatorLat, mercatorLng) {
    var x = (mercatorLng / 20037508.34) * 180;
    var y = (mercatorLat / 20037508.34) * 180;
    y = (180 / this.PI) * (2 * Math.atan(Math.exp((y * this.PI) / 180)) - this.PI / 2);
    return {
      lat: y,
      lng: x
    };
    /*
    if (Math.abs(mercatorLng) < 180 && Math.abs(mercatorLat) < 90)
        return null;
    if ((Math.abs(mercatorLng) > 20037508.3427892) || (Math.abs(mercatorLat) > 20037508.3427892))
        return null;
    var a = mercatorLng / 6378137.0 * 57.295779513082323;
    var x = a - (Math.floor(((a + 180.0) / 360.0)) * 360.0);
    var y = (1.5707963267948966 - (2.0 * Math.atan(Math.exp((-1.0 * mercatorLat) / 6378137.0)))) * 57.295779513082323;
    return {'lat' : y, 'lng' : x};
    //*/
  },
  /**
   * 两点之间的距离
   * @param  {number} latA 起点纬度
   * @param  {number} lngA 起点经度
   * @param  {number} latB 终点纬度
   * @param  {number} lngB 终点经度
   * @return {number}      返回距离(米)
   */
  distance: function (latA, lngA, latB, lngB) {
    var earthR = 6371000;
    var x = Math.cos((latA * this.PI) / 180) * Math.cos((latB * this.PI) / 180) * Math.cos(((lngA - lngB) * this.PI) / 180);
    var y = Math.sin((latA * this.PI) / 180) * Math.sin((latB * this.PI) / 180);
    var s = x + y;
    if (s > 1) s = 1;
    if (s < -1) s = -1;
    var alpha = Math.acos(s);
    var distance = alpha * earthR;
    return distance;
  },
  /**
   * 是否在中国之外
   * @param  {number} lat 纬度
   * @param  {number} lng 经度
   * @return {boolean]}     返回结果真或假
   */
  outOfChina: function (lat, lng) {
    if (lat < 72.004 || lat > 137.8347) return true;
    if (lng < 0.8293 || lng > 55.8271) return true;
    return false;
  },
  transformLat: function (x, y) {
    var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
    ret += ((20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0) / 3.0;
    ret += ((20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin((y / 3.0) * this.PI)) * 2.0) / 3.0;
    ret += ((160.0 * Math.sin((y / 12.0) * this.PI) + 320 * Math.sin((y * this.PI) / 30.0)) * 2.0) / 3.0;
    return ret;
  },
  transformLng: function (x, y) {
    var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
    ret += ((20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0) / 3.0;
    ret += ((20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin((x / 3.0) * this.PI)) * 2.0) / 3.0;
    ret += ((150.0 * Math.sin((x / 12.0) * this.PI) + 300.0 * Math.sin((x / 30.0) * this.PI)) * 2.0) / 3.0;
    return ret;
  }
};
web_src/src/components/Login.vue
@@ -29,14 +29,13 @@
      }
  },
  created(){
  var that = this;
  document.onkeydown = function(e) {
    var key = window.event.keyCode;
    if (key == 13) {
      that.login();
    var that = this;
    document.onkeydown = function(e) {
      var key = window.event.keyCode;
      if (key == 13) {
        that.login();
      }
    }
  }
  },
  methods:{
@@ -70,6 +69,7 @@
          if (res.data == "success") {
            that.$cookies.set("session", {"username": that.username}) ;
            //登录成功后
            that.cancelEnterkeyDefaultAction();
            that.$router.push('/');
          }else{
            that.isLoging = false;
@@ -84,18 +84,23 @@
        that.$message.error(error.response.statusText);
        that.isLoging = false;
      });
    },
     setCookie: function (cname, cvalue, exdays) {
                var d = new Date();
                d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
                var expires = "expires=" + d.toUTCString();
                console.info(cname + "=" + cvalue + "; " + expires);
                document.cookie = cname + "=" + cvalue + "; " + expires;
                console.info(document.cookie);
            },
    setCookie: function (cname, cvalue, exdays) {
      var d = new Date();
      d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
      var expires = "expires=" + d.toUTCString();
      console.info(cname + "=" + cvalue + "; " + expires);
      document.cookie = cname + "=" + cvalue + "; " + expires;
      console.info(document.cookie);
    },
    cancelEnterkeyDefaultAction: function() {
        document.onkeydown = function(e) {
        var key = window.event.keyCode;
        if (key == 13) {
          return false;
        }
      }
    }
  }
}
</script>
web_src/src/components/UiHeader.vue
@@ -34,7 +34,7 @@
            let that = this;
            if (this.alarmNotify) {
                console.log("申请SSE推送API调用,浏览器ID: " + this.$browserId);
                this.sseSource = new EventSource('/api/emit?browserId=' + this.$browserId);
                this.sseSource = new EventSource('/api/emit?browserId=' + this.$browserId);
                this.sseSource.addEventListener('message', function(evt) {
                    that.$notify({
                        title: '收到报警信息',
@@ -59,7 +59,7 @@
                this.sseSource.removeEventListener('message', null);
                this.sseSource.removeEventListener('error', null);
                this.sseSource.close();
            }
            }
        }
    },
    mounted() {
web_src/src/components/devicePosition.vue
New file
@@ -0,0 +1,388 @@
<template>
    <div id="devicePosition" style="height: 100%">
        <el-container style="height: 100%">
            <el-header>
                <uiHeader></uiHeader>
            </el-header>
            <el-main>
                <div style="background-color: #ffffff; position: relative; padding: 1rem 0.5rem 0.5rem 0.5rem; text-align: center;">
                    <span style="font-size: 1rem; font-weight: 500">设备定位 ({{ parentChannelId == 0 ? deviceId : parentChannelId }})</span>
                </div>
                <div style="background-color: #ffffff; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left; font-size: 14px;">
                    <el-button icon="el-icon-arrow-left" size="mini" style="margin-right: 1rem" type="primary" @click="showDevice">返回</el-button>
                    <!-- <span class="demonstration">从</span> -->
                    <el-date-picker v-model="searchFrom" type="datetime" placeholder="选择开始日期时间" default-time="00:00:00" size="mini" style="width: 11rem;" align="right" :picker-options="pickerOptions"></el-date-picker>
                    <el-date-picker v-model="searchTo" type="datetime" placeholder="选择结束日期时间" default-time="00:00:00" size="mini" style="width: 11rem;" align="right" :picker-options="pickerOptions"></el-date-picker>
                    <el-button-group>
                        <el-button icon="el-icon-search" size="mini" type="primary" @click="showHistoryPath">历史轨迹</el-button>
                        <el-button icon="el-icon-search" size="mini" style="margin-right: 1rem" type="primary" @click="showLatestPosition">最新位置</el-button>
                    </el-button-group>
                    <el-tag style="width: 5rem; text-align: center" size="medium">过期时间</el-tag>
                    <el-input-number size="mini" v-model="expired" :min="300" :controls="false" style="width: 4rem;"></el-input-number>
                    <el-tag style="width: 5rem; text-align: center" size="medium">上报周期</el-tag>
                    <el-input-number size="mini" v-model="interval" :min="1" :controls="false" style="width: 4rem;"></el-input-number>
                    <el-button-group>
                        <el-button icon="el-icon-search" size="mini" type="primary" @click="subscribeMobilePosition">位置订阅</el-button>
                        <el-button icon="el-icon-search" size="mini" type="primary" @click="unSubscribeMobilePosition">取消订阅</el-button>
                    </el-button-group>
                    <el-checkbox size="mini" style="margin-right: 1rem; float: right" v-model="autoList" @change="autoListChange" >自动刷新</el-checkbox>
                </div>
                <div class="mapContainer" style="background-color: #ffffff; position: relative; padding: 1rem 0.5rem 0.5rem 0.5rem; text-align: center; height: calc(100% - 10rem);">
                    <div class="baidumap" id="allmap"></div>
                </div>
            </el-main>
        </el-container>
    </div>
</template>
<script>
import uiHeader from "./UiHeader.vue";
import moment from "moment";
import geoTools from "./GeoConvertTools.js";
export default {
    name: "devicePosition",
    components: {
        uiHeader,
    },
    data() {
        return {
            pickerOptions: {
                shortcuts: [{
                    text: '今天',
                    onClick(picker) {
                        picker.$emit('pick', new Date());
                    }
                }, {
                    text: '昨天',
                    onClick(picker) {
                        const date = new Date();
                        date.setTime(date.getTime() - 3600 * 1000 * 24);
                        picker.$emit('pick', date);
                    }
                }, {
                    text: '一周前',
                    onClick(picker) {
                        const date = new Date();
                        date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
                        picker.$emit('pick', date);
                    }
                }]
            },
            deviceId: this.$route.params.deviceId,
            showHistoryPosition: false, //显示历史轨迹
            startTime: null,
            endTime: null,
            searchFrom: null,
            searchTo: null,
            expired: 600,
            interval: 5,
            mobilePositionList: [],
            mapPointList: [],
            parentChannelId: this.$route.params.parentChannelId,
            updateLooper: 0, //数据刷新轮训标志
            total: 0,
            beforeUrl: "/videoList",
            isLoging: false,
            autoList: false,
        };
    },
    mounted() {
        this.initData();
        this.initBaiduMap();
        if (this.autoList) {
            this.updateLooper = setInterval(this.initData, 5000);
        }
    },
    destroyed() {
        // this.$destroy("videojs");
        clearTimeout(this.updateLooper);
    },
    methods: {
        initData: function () {
            // if (this.parentChannelId == "" || this.parentChannelId == 0) {
            //     this.getDeviceChannelList();
            // } else {
            //     this.showSubchannels();
            // }
        },
        initParam: function () {
            // this.deviceId = this.$route.params.deviceId;
            // this.parentChannelId = this.$route.params.parentChannelId;
            // this.currentPage = parseInt(this.$route.params.page);
            // this.count = parseInt(this.$route.params.count);
            // if (this.parentChannelId == "" || this.parentChannelId == 0) {
            //     this.beforeUrl = "/videoList";
            // }
        },
        initBaiduMap() {
            this.map = new BMap.Map("allmap"); // 创建地图实例
            let points = [];
            let point = new BMap.Point(116.231398, 39.567445); // 创建点坐标
            this.map.centerAndZoom(point, 5); // 初始化地图,设置中心点坐标和地图级别
            this.map.enableScrollWheelZoom(true); //开启鼠标滚轮缩放
            this.map.addControl(new BMap.NavigationControl());
            this.map.addControl(new BMap.ScaleControl());
            this.map.addControl(new BMap.OverviewMapControl());
            this.map.addControl(new BMap.MapTypeControl());
            //map.setMapStyle({ style: 'midnight' }) //地图风格
        },
        currentChange: function (val) {
            // var url = `/${this.$router.currentRoute.name}/${this.deviceId}/${this.parentChannelId}/${this.count}/${val}`;
            // console.log(url);
            // this.$router.push(url).then(() => {
            //     this.initParam();
            //     this.initData();
            // });
        },
        handleSizeChange: function (val) {
            // var url = `/${this.$router.currentRoute.name}/${this.$router.params.deviceId}/${this.$router.params.parentChannelId}/${val}/1`;
            // this.$router.push(url).then(() => {
            //     this.initParam();
            //     this.initData();
            // });
        },
        showDevice: function () {
            this.$router.push(this.beforeUrl).then(() => {
                this.initParam();
                this.initData();
            });
        },
        autoListChange: function () {
            if (this.autoList) {
                this.updateLooper = setInterval(this.initData, 1500);
            } else {
                window.clearInterval(this.updateLooper);
            }
        },
        showHistoryPath: function () {
            this.map.clearOverlays();
            this.mapPointList = [];
            this.mobilePositionList = [];
            if (!!this.searchFrom) {
                this.startTime = this.toGBString(this.searchFrom);
                console.log(this.startTime);
            } else{
                this.startTime = null;
            }
            if (!!this.searchTo) {
                this.endTime = this.toGBString(this.searchTo);
                console.log(this.endTime);
            } else {
                this.endTime = null;
            }
            let self = this;
            this.$axios.get(`/api/positions/${this.deviceId}/history`, {
                params: {
                    start: self.startTime,
                    end: self.endTime,
                },
            })
            .then(function (res) {
                self.total = res.data.length;
                self.mobilePositionList = res.data;
                console.log(self.mobilePositionList);
                if (self.total == 0) {
                    self.$message({
                        showClose: true,
                        message: '未找到符合条件的移动位置信息',
                        type: 'error'
                    });
                } else {
                    self.$nextTick(() => {
                        self.showMarkPoints(self);
                    });
                }
            })
            .catch(function (error) {
                console.log(error);
            });
        },
        showLatestPosition: function() {
            this.map.clearOverlays();
            this.mapPointList = [];
            this.mobilePositionList = [];
            let self = this;
            this.$axios.get(`/api/positions/${this.deviceId}/latest`)
            .then(function (res) {
                console.log(res.data);
                self.total = res.data.length;
                self.mobilePositionList.push(res.data);
                console.log(self.mobilePositionList);
                if (self.total == 0) {
                    self.$message({
                        showClose: true,
                        message: '未找到符合条件的移动位置信息',
                        type: 'error'
                    });
                } else {
                    self.$nextTick(() => {
                        self.showMarkPoints(self);
                    });
                }
            })
            .catch(function (error) {
                console.log(error);
            });
        },
        subscribeMobilePosition: function() {
            let self = this;
            this.$axios.get(`/api/positions/${this.deviceId}/subscribe`, {
                params: {
                    expires: self.expired,
                    interval: self.interval,
                },
            })
            .then(function (res) {
                console.log(res.data);
            })
            .catch(function (error) {
                console.log(error);
            });
        },
        unSubscribeMobilePosition: function() {
            let self = this;
            this.$axios.get(`/api/positions/${this.deviceId}/subscribe`, {
                params: {
                    expires: 0,
                    interval: self.interval,
                },
            })
            .then(function (res) {
                console.log(res.data);
            })
            .catch(function (error) {
                console.log(error);
            });
        },
        toGBString: function (dateTime) {
            return (
                dateTime.getFullYear() +
                "-" + this.twoDigits(dateTime.getMonth() + 1) +
                "-" + this.twoDigits(dateTime.getDate()) +
                "T" + this.twoDigits(dateTime.getHours()) +
                ":" + this.twoDigits(dateTime.getMinutes()) +
                ":" + this.twoDigits(dateTime.getSeconds())
            );
        },
        twoDigits: function (num) {
            if (num < 10) {
                return "0" + num;
            } else {
                return "" + num;
            }
        },
        showMarkPoints: function(self) {
            let that = self;
            let npointJ = null;
            let npointW = null;
            let point = null;
            for (let i = 0; i < self.mobilePositionList.length; i++) {
                if (self.mobilePositionList[i].geodeticSystem == "BD-09") {
                    npointJ = self.mobilePositionList[i].cnLng;
                    npointW = self.mobilePositionList[i].cnLat;
                    point = new BMap.Point(npointJ, npointW);
                } else {
                    npointJ = self.mobilePositionList[i].longitude;
                    npointW = self.mobilePositionList[i].latitude;
                    let bd2 = geoTools.GPSToBaidu(npointJ, npointW);
                    point = new BMap.Point(bd2.lat, bd2.lng);
                }
                self.mapPointList.push(point);
                let marker = new BMap.Marker(point); // 创建标注
                self.map.addOverlay(marker); // 将标注添加到地图中
                //提示信息  可以解析 HTML标签以及CSS
                let infoWindow = new BMap.InfoWindow(`<p style='text-align:left;font-weight:800'>设备: ${self.mobilePositionList[i].deviceId}</p>
                            <p style='text-align:left;font-weight:0'>时间: ${self.mobilePositionList[i].time}</p>`);
                // 鼠标移上标注点要发生的事
                marker.addEventListener("mouseover", function () {
                    this.openInfoWindow(infoWindow);
                });
                // 鼠标移开标注点要发生的事
                marker.addEventListener("mouseout", function () {
                    this.closeInfoWindow(infoWindow);
                });
                // 鼠标点击标注点要发生的事情
                marker.addEventListener("click", function () {
                    alert("点击");
                });
            }
            let view = that.map.getViewport(eval(self.mapPointList));
            that.map.centerAndZoom(view.center, view.zoom);
        },
    },
};
</script>
<style>
.videoList {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
}
.video-item {
  position: relative;
  width: 15rem;
  height: 10rem;
  margin-right: 1rem;
  background-color: #000000;
}
.video-item-img {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  width: 100%;
  height: 100%;
}
.video-item-img:after {
  content: "";
  display: inline-block;
  position: absolute;
  z-index: 2;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  width: 3rem;
  height: 3rem;
  background-image: url("../assets/loading.png");
  background-size: cover;
  background-color: #000000;
}
.video-item-title {
  position: absolute;
  bottom: 0;
  color: #000000;
  background-color: #ffffff;
  line-height: 1.5rem;
  padding: 0.3rem;
  width: 14.4rem;
}
.baidumap {
  width: 100%;
  height: 100%;
  border: none;
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}
/* 去除百度地图版权那行字 和 百度logo */
.baidumap > .BMap_cpyCtrl {
  display: none !important;
}
.baidumap > .anchorBL {
  display: none !important;
}
</style>
web_src/src/components/videoList.vue
@@ -11,7 +11,7 @@
                        <el-button icon="el-icon-refresh-right" circle size="mini" :loading="getDeviceListLoading" @click="getDeviceList()"></el-button>
                    </div>
                </div>
                <devicePlayer ref="devicePlayer"></devicePlayer>
                <!-- <devicePlayer ref="devicePlayer"></devicePlayer> -->
                <!--设备列表-->
                <el-table :data="deviceList" border style="width: 100%" :height="winHeight">
                    <el-table-column prop="name" label="名称" width="180" align="center">
@@ -40,7 +40,7 @@
                    </el-table-column>
                    <el-table-column prop="channelCount" label="通道数" align="center">
                    </el-table-column>
                    <el-table-column label="状态" width="180" align="center">
                    <el-table-column label="状态" width="80" align="center">
                        <template slot-scope="scope">
                            <div slot="reference" class="name-wrapper">
                                <el-tag size="medium" v-if="scope.row.online == 1">在线</el-tag>
@@ -49,11 +49,15 @@
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" width="240" align="center" fixed="right">
                    <el-table-column label="操作" width="360" align="center" fixed="right">
                        <template slot-scope="scope">
                            <el-button size="mini" :ref="scope.row.deviceId + 'refbtn' " icon="el-icon-refresh"  @click="refDevice(scope.row)">刷新通道</el-button>
                            <el-button size="mini" icon="el-icon-s-open" v-bind:disabled="scope.row.online==0"  type="primary" @click="showChannelList(scope.row)">查看通道</el-button>
                        </template>
                            <el-button size="mini" :ref="scope.row.deviceId + 'refbtn' " icon="el-icon-refresh"  @click="refDevice(scope.row)">刷新</el-button>
                            <el-button-group>
                            <el-button size="mini" icon="el-icon-video-camera-solid" v-bind:disabled="scope.row.online==0"  type="primary" @click="showChannelList(scope.row)">通道</el-button>
                            <el-button size="mini" icon="el-icon-location" v-bind:disabled="scope.row.online==0"  type="primary" @click="showDevicePosition(scope.row)">定位</el-button>
                            <el-button size="mini" icon="el-icon-s-tools" v-bind:disabled="scope.row.online==0"  type="primary">控制</el-button>
                            </el-button-group>
                            </template>
                    </el-table-column>
                </el-table>
                <el-pagination
@@ -73,7 +77,7 @@
</template>
<script>
     import uiHeader from './UiHeader.vue'
    import uiHeader from './UiHeader.vue'
    export default {
        name: 'app',
        components: {
@@ -155,7 +159,10 @@
                console.log(JSON.stringify(row))
                this.$router.push(`/channelList/${row.deviceId}/0/15/1`);
            },
            showDevicePosition: function(row) {
                console.log(JSON.stringify(row))
                this.$router.push(`/devicePosition/${row.deviceId}/0/15/1`);
            },
            //gb28181平台对接
            //刷新设备信息
@@ -191,18 +198,18 @@
            },
            //通知设备上传媒体流
            sendDevicePush: function(itemData) {
                let deviceId = this.currentDevice.deviceId;
                let channelId = itemData.channelId;
                console.log("通知设备推流1:" + deviceId + " : " + channelId);
                let that = this;
                this.$axios({
                    method: 'get',
                    url: '/api/play/' + deviceId + '/' + channelId
                }).then(function(res) {
                    let ssrc = res.data.ssrc;
                    that.$refs.devicePlayer.play(ssrc,deviceId,channelId);
                }).catch(function(e) {
                });
                // let deviceId = this.currentDevice.deviceId;
                // let channelId = itemData.channelId;
                // console.log("通知设备推流1:" + deviceId + " : " + channelId);
                // let that = this;
                // this.$axios({
                //     method: 'get',
                //     url: '/api/play/' + deviceId + '/' + channelId
                // }).then(function(res) {
                //     let ssrc = res.data.ssrc;
                //     that.$refs.devicePlayer.play(ssrc,deviceId,channelId);
                // }).catch(function(e) {
                // });
            },
      transportChange: function (row) {
        console.log(row);
web_src/src/main.js
@@ -12,21 +12,21 @@
import { Notification } from 'element-ui';
import Fingerprint2 from 'fingerprintjs2';
// 生成唯一ID
// 生成唯一ID
Fingerprint2.get(function(components) {
  const values = components.map(function(component,index) {
    if (index === 0) { //把微信浏览器里UA的wifi或4G等网络替换成空,不然切换网络会ID不一样
    if (index === 0) { //把微信浏览器里UA的wifi或4G等网络替换成空,不然切换网络会ID不一样
      return component.value.replace(/\bNetType\/\w+\b/, '');
    }
    return component.value;
  })
  //console.log(values)  //使用的浏览器信息npm
  // 生成最终id
  //console.log(values)  //使用的浏览器信息npm
  // 生成最终id
  let port = window.location.port;
  console.log(port);
  const fingerPrint = Fingerprint2.x64hash128(values.join(port), 31)
  Vue.prototype.$browserId = fingerPrint;
  console.log("唯一标识码:" + fingerPrint);
  console.log("唯一标识码:" + fingerPrint);
});
Vue.use(VueClipboard);
web_src/src/router/index.js
@@ -4,6 +4,7 @@
import control from '../components/control.vue'
import videoList from '../components/videoList.vue'
import channelList from '../components/channelList.vue'
import devicePosition from  '../components/devicePosition.vue'
import login from '../components/Login.vue'
import parentPlatformList from '../components/ParentPlatformList.vue'
@@ -41,5 +42,10 @@
      name: 'parentPlatformList',
      component: parentPlatformList,
    },
    {
      path: '/devicePosition/:deviceId/:parentChannelId/:count/:page',
      name: 'devicePosition',
      component: devicePosition,
    },
  ]
})