kindring 2 år sedan
förälder
incheckning
43f3f14862
65 ändrade filer med 4172 tillägg och 618 borttagningar
  1. 12 0
      install.sh
  2. 6 3
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  3. 33 0
      src/main/java/com/genersoft/iot/vmp/common/HfyAiInfo.java
  4. 11 0
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  5. 1 1
      src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
  6. 5 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  7. 21 31
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  8. 6 2
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
  9. 74 2
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
  10. 19 0
      src/main/java/com/genersoft/iot/vmp/storager/IAiControlStorage.java
  11. 7 1
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  12. 125 2
      src/main/java/com/genersoft/iot/vmp/storager/dao/HfyDevAiMapper.java
  13. 91 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/AiControlStorageImpl.java
  14. 108 11
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  15. 172 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/AiAlarm.java
  16. 246 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/AiAlarmData.java
  17. 122 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/AiRequestBody.java
  18. 272 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/BodyAiAlarm.java
  19. 3 3
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java
  20. 19 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/RecoInfo.java
  21. 123 6
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/UploadService.java
  22. 49 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java
  23. 76 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/aiLib/AiApi.java
  24. 159 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/aiLib/AiControl.java
  25. 1 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java
  26. 25 1
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
  27. 17 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
  28. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
  29. 7 3
      src/main/resources/application.yml
  30. 24 7
      src/main/resources/banner.txt
  31. 2 0
      src/main/resources/logback-spring-local.xml
  32. 5 0
      web_src/config/index.js
  33. 1 1
      web_src/index.html
  34. 32 0
      web_src/src/apiStore/api_user.js
  35. 47 0
      web_src/src/apiStore/axionsBefore.js
  36. 66 0
      web_src/src/apiStore/axios.js
  37. 35 0
      web_src/src/assets/index.css
  38. 33 34
      web_src/src/components/Login.vue
  39. 19 5
      web_src/src/components/aiConfig.vue
  40. 299 0
      web_src/src/components/bell.vue
  41. 5 2
      web_src/src/components/channelList.vue
  42. 394 0
      web_src/src/components/common/ptzControl.vue
  43. 66 0
      web_src/src/components/common/signalInfo.vue
  44. 3 4
      web_src/src/components/createConfig.vue
  45. 1 1
      web_src/src/components/dialog/addArithmetic.vue
  46. 435 0
      web_src/src/components/dialog/customPlayer.vue
  47. 3 59
      web_src/src/components/dialog/devicePlayer.vue
  48. 231 0
      web_src/src/components/dialog/dialogPtzControl.vue
  49. 0 328
      web_src/src/components/dialog/ptzControl.vue
  50. 41 0
      web_src/src/components/layoutCom/user_header.vue
  51. 232 0
      web_src/src/components/mediaView.vue
  52. 67 0
      web_src/src/components/u_page/u_info.vue
  53. 50 0
      web_src/src/hfyUntil/devTool.js
  54. 27 9
      web_src/src/layout/UiHeader.vue
  55. 1 16
      web_src/src/layout/index.vue
  56. 34 0
      web_src/src/layout/userCenter.vue
  57. 2 15
      web_src/src/main.js
  58. 26 5
      web_src/src/map/ai.js
  59. 34 5
      web_src/src/router/index.js
  60. 6 0
      web_src/src/until/EventTool.js
  61. 1 0
      web_src/src/until/handle.js
  62. 47 0
      web_src/src/until/time.js
  63. BIN
      web_src/static/favicon.ico
  64. 62 48
      参考文档/数据库扩展.md
  65. 30 6
      参考文档/订阅通知实现ai识别.md

+ 12 - 0
install.sh

@@ -0,0 +1,12 @@
+#!/bin/bash
+
+echo "
+-----------------------
+gb28181一键化解决方案
+-----------------------
+";
+if [[ $(ssh -l root "$1" 'docker -v') == *" 1.7."* ]]; then
+    echo 'Docker version 1.7!'
+else
+    echo 'Docker version not 1.7!'
+fi

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

@@ -32,9 +32,12 @@ public class VManageBootstrap extends LogManager {
 		VManageBootstrap.args = args;
 		VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
 		GitUtil gitUtil1 = SpringBeanFactory.getBean("gitUtil");
-		logger.info("构建版本: {}", gitUtil1.getBuildVersion());
-		logger.info("构建时间: {}", gitUtil1.getBuildDate());
-		logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime());
+		logger.info("--------------------------------------------------");
+		logger.info("------------- HFY GB Server START ----------------");
+		logger.info("--------------------------------------------------");
+//		logger.info("构建版本: {}", gitUtil1.getBuildVersion());
+//		logger.info("构建时间: {}", gitUtil1.getBuildDate());
+//		logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime());
 	}
 	// 项目重启
 	public static void restart() {

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/common/HfyAiInfo.java

@@ -17,6 +17,35 @@ public class HfyAiInfo {
     private TriggerType triggerType = TriggerType.noList;
     private int refreshTime = 60;
     private float score = 70;
+
+
+    public void setArithmetic(Arithmetic arithmetic) {
+        this.arithmetic = arithmetic;
+    }
+
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+
+    public void setUploadUrl(String uploadUrl) {
+        this.uploadUrl = uploadUrl;
+    }
+
+    public void setTriggerType(TriggerType triggerType) {
+        this.triggerType = triggerType;
+    }
+    public void setTriggerTypeInt(int triggerType){
+        this.triggerType = TriggerType.values()[triggerType];
+    }
+
+    public void setRefreshTime(int refreshTime) {
+        this.refreshTime = refreshTime;
+    }
+
+    public void setScore(float score) {
+        this.score = score;
+    }
+
     // 构造方法
 
     /**
@@ -27,6 +56,10 @@ public class HfyAiInfo {
         super();
         this.arithmetic = arithmetic;
     }
+    public HfyAiInfo(int arithmetic) {
+        super();
+        this.arithmetic = Arithmetic.values()[arithmetic];
+    }
     public HfyAiInfo(Arithmetic arithmetic,TriggerType triggerType,String resourcePath,String uploadUrl,int refreshTime,float score){
         super();
         this.arithmetic = arithmetic;

+ 11 - 0
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java

@@ -22,6 +22,8 @@ public class SipConfig {
 	private String id;
 
 	private String password;
+
+	private String mediaPath = "mFile";
 	
 	Integer ptzSpeed = 50;
 
@@ -29,6 +31,7 @@ public class SipConfig {
 
 	Integer registerTimeInterval = 120;
 
+
 	private boolean alarm;
 
 	public void setIp(String ip) {
@@ -114,4 +117,12 @@ public class SipConfig {
 	public void setAlarm(boolean alarm) {
 		this.alarm = alarm;
 	}
+
+	public String getMediaPath() {
+		return this.mediaPath;
+	}
+
+	public void setMediaPath(String mediaPath) {
+		this.mediaPath = mediaPath;
+	}
 }

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

@@ -131,7 +131,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         http.headers().contentTypeOptions().disable();
         http.authorizeRequests()
                 // 放行接口
-                .antMatchers("/api/user/login","/index/hook/**","/aiLib/**").permitAll()
+                .antMatchers("/api/user/login","/index/hook/**","/aiLib/**","/aiLib/mFile/**").permitAll()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 // 异常处理(权限拒绝、登录失效等)

+ 5 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -16,10 +16,10 @@ import javax.sip.PeerUnavailableException;
 import javax.sip.SipException;
 import javax.sip.message.Request;
 import java.text.ParseException;
+import java.util.List;
 
 
-
-/**    
+/**
  * @description:设备能力接口,用于定义设备的控制、查询能力   
  * @author: swwheihei
  * @date:   2020年5月3日 下午9:16:34     
@@ -64,7 +64,8 @@ public interface ISIPCommander {
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
 	 */
 	void ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed) throws InvalidArgumentException, ParseException, SipException;
-	
+
+	public void ptzFocus(Device device, String channelId) throws InvalidArgumentException, ParseException, SipException;
 	/**
 	 * 云台控制,支持方向与缩放控制
 	 * 
@@ -348,7 +349,7 @@ public interface ISIPCommander {
 	 * @param endTime
 	 * @param aiDataList
 	 */
-	void hfyAiAlarmSubScribe(Device device, int expires, String startTime, String endTime, HfyAiInfo[] aiDataList) throws InvalidArgumentException, SipException, ParseException;
+	SIPRequest hfyAiAlarmSubScribe(Device device, int expires, String startTime, String endTime,  List<HfyAiInfo> aiDataList,SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 订阅、取消订阅目录信息

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

@@ -3,7 +3,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.HfyAiInfo;
 import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
@@ -21,35 +20,23 @@ import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.GitUtil;
-import gov.nist.javax.sip.SIPConstants;
 import gov.nist.javax.sip.SipProviderImpl;
-import gov.nist.javax.sip.SipStackImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
-import gov.nist.javax.sip.stack.SIPClientTransaction;
-import gov.nist.javax.sip.stack.SIPClientTransactionImpl;
-import gov.nist.javax.sip.stack.SIPDialog;
 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.DependsOn;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
 import javax.sip.*;
-import javax.sip.address.Address;
-import javax.sip.address.SipURI;
 import javax.sip.header.*;
 import javax.sip.message.Request;
-import javax.sip.message.Response;
-import java.lang.reflect.Field;
 import java.text.ParseException;
-import java.util.HashSet;
+import java.util.List;
 
 /**
  * @description:设备能力接口,用于定义设备的控制、查询能力
@@ -137,6 +124,17 @@ public class SIPCommander implements ISIPCommander {
         ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getPtzSpeed());
     }
 
+    /**
+     * 云台缩放控制,使用配置文件中的默认镜头缩放速度
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     */
+    @Override
+    public void ptzFocus(Device device, String channelId) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, 0, 0, 3, 0, sipConfig.getPtzSpeed());
+    }
+
     /**
      * 云台缩放控制
      *
@@ -183,7 +181,7 @@ public class SIPCommander implements ISIPCommander {
      * @param channelId 预览通道
      * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
-     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大 3:聚焦
      * @param moveSpeed 镜头移动速度
      * @param zoomSpeed 镜头缩放速度
      */
@@ -1307,18 +1305,9 @@ public class SIPCommander implements ISIPCommander {
         return request;
     }
 
-    /**
-     * 订阅HFY设备ai识别信息
-     *
-     * @param device
-     * @param expires
-     * @param startTime
-     * @param endTime
-     * @param aiDataList
-     * @return true = 命令发送成功
-     */
+
     @Override
-    public void hfyAiAlarmSubScribe(Device device, int expires, String startTime, String endTime, HfyAiInfo[] aiDataList)throws InvalidArgumentException, SipException, ParseException  {
+    public SIPRequest hfyAiAlarmSubScribe(Device device, int expires, String startTime, String endTime, List<HfyAiInfo> aiDataList, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent)throws InvalidArgumentException, SipException, ParseException  {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1329,9 +1318,9 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("<StartAlarmPriority>" + 4 + "</StartAlarmPriority>\r\n");
         cmdXml.append("<EndAlarmPriority>" + 4 + "</EndAlarmPriority>\r\n");
         cmdXml.append("<AlarmMethod>" + 7 + "</AlarmMethod>\r\n");
-        cmdXml.append("<HfyAiInfo aiNum"+aiDataList.length+">\r\n");
-        for (int i=0; i<aiDataList.length; i++) {
-            HfyAiInfo aiData = aiDataList[i];
+        cmdXml.append("<HfyAiInfo aiNum"+aiDataList.size()+">\r\n");
+        for (int i=0; i<aiDataList.size(); i++) {
+            HfyAiInfo aiData = aiDataList.get(i);
             cmdXml.append("<HfyAi>\r\n");
             //枚举值+1,以保证各地一样
             cmdXml.append("<Arithmetic>" + (aiData.getArithmeticInt() + 1) + "</Arithmetic>\r\n");
@@ -1361,8 +1350,9 @@ public class SIPCommander implements ISIPCommander {
         callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
                 : udpSipProvider.getNewCallId();
 
-        Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "hfyAi", callIdHeader);
-        transmitRequest(device.getTransport(), request);
+        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "hfyAi", callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        return request;
     }
 
     @Override

+ 6 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java

@@ -76,7 +76,7 @@ public class SipUtils {
      *
      * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大 3:聚焦
      * @param moveSpeed  镜头移动速度 默认 0XFF (0-255)
      * @param zoomSpeed  镜头缩放速度 默认 0X1 (0-255)
      */
@@ -96,8 +96,12 @@ public class SipUtils {
             cmdCode |= 0x10;	// 放大
         } else if(inOut == 1) {
             cmdCode |= 0x20;	// 缩小
+        }else if(inOut == 3){
+            // TODO: 2022/12/13 聚焦code 待商榷
+            cmdCode |= 0x30; // 聚焦
         }
-        StringBuilder builder = new StringBuilder("A5 0F 01");
+
+        StringBuilder builder = new StringBuilder("A50F01");
         //A50F01 A5 0F 01
         String strTmp;
         strTmp = String.format("%02X", cmdCode);

+ 74 - 2
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java

@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.service.impl;
 
+import com.genersoft.iot.vmp.common.HfyAiInfo;
 import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
@@ -16,8 +18,10 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
 import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
+import com.genersoft.iot.vmp.storager.dao.HfyDevAiMapper;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.vmanager.bean.BaseTree;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -27,7 +31,9 @@ import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
 import javax.sip.SipException;
+import javax.sip.header.ToHeader;
 import java.text.ParseException;
 import java.time.Instant;
 import java.util.ArrayList;
@@ -44,7 +50,7 @@ public class DeviceServiceImpl implements IDeviceService {
     private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class);
 
     private final String  registerExpireTaskKeyPrefix = "device-register-expire-";
-
+    private SIPRequest request;
     @Autowired
     private DynamicTask dynamicTask;
 
@@ -60,6 +66,9 @@ public class DeviceServiceImpl implements IDeviceService {
     @Autowired
     private DeviceMapper deviceMapper;
 
+    @Autowired
+    private HfyDevAiMapper hfyDevAiMapper;
+
     @Autowired
     private IDeviceChannelService deviceChannelService;
 
@@ -78,9 +87,13 @@ public class DeviceServiceImpl implements IDeviceService {
     @Autowired
     private IMediaServerService mediaServerService;
 
+    @Autowired
+    private SipConfig sipConfig;
+
     @Override
     public void online(Device device) {
         logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
+        logger.info("[test] sipBaseUrl={}:{}",sipConfig.getIp(),sipConfig.getPort());
         Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId());
         Device deviceInDb = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
 
@@ -117,7 +130,62 @@ public class DeviceServiceImpl implements IDeviceService {
                     logger.error("[命令发送失败] 查询设备信息: {}", e.getMessage());
                 }
                 sync(device);
+
                 // TODO 如果设备下的通道级联到了其他平台,那么需要发送事件或者notify给上级平台
+                // 查询设备是否绑定ai配置
+                List<AiConfig> devAiConfigs = hfyDevAiMapper.getDevAiConfigByDeviceId(device.getDeviceId());
+                logger.info("[ai订阅检测]成功: size:{}",devAiConfigs.size());
+                if(devAiConfigs.size() >0){
+                    // aiDataList
+                    List<HfyAiInfo> aiDataList = new ArrayList<HfyAiInfo>();
+
+                    // 循环
+                    for (int i = 0; i < devAiConfigs.size(); i++) {
+                        AiConfig devConfig = devAiConfigs.get(i);
+                        HfyAiInfo aiItem = new HfyAiInfo(devConfig.getArithmetic());
+                        //
+                        aiItem.setScore(devConfig.getScore());
+                        aiItem.setTriggerTypeInt(devConfig.getTriggerType());
+                        aiItem.setRefreshTime(devConfig.getRefreshTime());
+                        // 查看是否有上传地址等设置
+                        if(!devConfig.getResourcePath().isEmpty()){
+                            aiItem.setResourcePath(devConfig.getResourcePath());
+                        }else{
+                            // 当前ip地址
+                            //
+                            String resourcePath = sipConfig.getIp()+":"+sipConfig.getPort()+"/aiLib/list?libId="+devConfig.getLibraryId();
+                            aiItem.setResourcePath(resourcePath);
+                        }
+                        aiDataList.add(aiItem);
+                    }
+                    SIPRequest sipRequest = null;
+                    int expires = 36000;
+                    String startTime = "25900";
+                    String endTime = "2590222";
+                    try {
+                        sipRequest = sipCommander.hfyAiAlarmSubScribe(device,expires,startTime,endTime,aiDataList, eventResult -> {
+                            ResponseEvent event = (ResponseEvent) eventResult.event;
+                            // 成功
+                            logger.info("[ai订阅]成功: {}", device.getDeviceId());
+                            ToHeader toHeader = (ToHeader)event.getResponse().getHeader(ToHeader.NAME);
+                            //logger.info("[ai订阅]成功: {}", device.getDeviceId());
+                            try {
+                                this.request.getToHeader().setTag(toHeader.getTag());
+                            } catch (ParseException e) {
+                                logger.info("[ai订阅]成功: 但为request设置ToTag失败");
+                                this.request = null;
+                            }
+                        },eventResult -> {
+//                        this.request = null;
+                            // 失败
+                            logger.warn("[ai订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+                        });
+                    } catch (InvalidArgumentException | SipException | ParseException e) {
+                        logger.error("[命令发送失败] ai订阅: {}", e.getMessage());
+
+                    }
+                }
+
             }else {
                 deviceMapper.update(device);
                 redisCatchStorage.updateDevice(device);
@@ -130,13 +198,17 @@ public class DeviceServiceImpl implements IDeviceService {
             // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
             addCatalogSubscribe(device);
         }
+
         if (device.getSubscribeCycleForMobilePosition() > 0) {
             addMobilePositionSubscribe(device);
         }
         // 刷新过期任务
         String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId();
-//      // 设备过期时间多增加3
+        // 设备过期时间为双倍多加5
         dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), ((device.getExpires() * 1000) * 2 ) + 5);
+
+
+
     }
 
     @Override

+ 19 - 0
src/main/java/com/genersoft/iot/vmp/storager/IAiControlStorage.java

@@ -0,0 +1,19 @@
+package com.genersoft.iot.vmp.storager;
+
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarm;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarmData;
+import com.github.pagehelper.PageInfo;
+import org.springframework.stereotype.Component;
+
+/**
+ * @description:ai相关接口
+ * @author: kindring
+ * @date:
+ */
+@SuppressWarnings("rawtypes")
+public interface IAiControlStorage {
+    public PageInfo<AiAlarmData> searchAiAlarm(String startTime, String endTime, String order, String sort, int p, int l);
+    public AiAlarmData getAlarmData(String alarmId);
+
+    public int changeAiAlarm(String alarmId,String cmder);
+}

+ 7 - 1
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarm;
 import com.genersoft.iot.vmp.vmanager.bean.AiLib;
 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
 import com.github.pagehelper.PageInfo;
@@ -85,6 +86,9 @@ public interface IVideoManagerStorage {
 	 */
 	public String addLibItem(int libraryId, String itemName, String itemNo, String card , MultipartFile file);
 
+	public String editLibItem(int itemId, String itemName, String itemNo, String card , MultipartFile file);
+
+
 	public int addAiConfig(String configName,
 						   int arithmetic,
 						   int triggerType,
@@ -101,6 +105,7 @@ public interface IVideoManagerStorage {
 	 */
 	public List<AiConfig> queryItemByLibraryId(int libraryId);
 
+	public String queryAiLibVersion(String libId);
 	/**
 	 * 获取item信息
 	 * @param itemId
@@ -157,7 +162,8 @@ public interface IVideoManagerStorage {
 	public PageInfo<AiLib> searchAiLibItems(int page,int count,int libraryId,String key);
 	public PageInfo<AiLib> searchAiLib(int page,int count,String key,Integer arithmetic);
 
-
+	public int saveAlarm(String deviceId, String channelId, String arithmetic, List<AiAlarm> alarmItems,List<MultipartFile> uploads,
+						 String firmware_version,String timestamp,String battery,String signal,String temp_env,String temp_cpu,String ccid);
 	/**
 	 * 获取多个设备
 	 *

+ 125 - 2
src/main/java/com/genersoft/iot/vmp/storager/dao/HfyDevAiMapper.java

@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.storager.dao;
 import com.genersoft.iot.vmp.gb28181.bean.AiConfig;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarm;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarmData;
 import com.genersoft.iot.vmp.vmanager.bean.AiLib;
 import org.apache.ibatis.annotations.*;
 import org.springframework.stereotype.Repository;
@@ -13,7 +15,8 @@ import java.util.List;
 @Repository
 public interface HfyDevAiMapper {
     @Select("select c.* from ai_configs as c " +
-            "left Join dev_ai_config as dc on dc.configId = c.configId " +
+            "left Join dev_ai_config as dc on (dc.configId = c.configId) " +
+            "left Join ai_library as ai on (ai.libraryId = c.libraryId) " +
             "where dc.deviceId = #{deviceId}")
     List<AiConfig> getDevAiConfigByDeviceId(String deviceId);
     @Select("select * from ai_configs where arithmetic = ${arithmetic}")
@@ -90,6 +93,9 @@ public interface HfyDevAiMapper {
     @Select("select * from ai_library where libraryId = #{libraryId}")
     List<AiLib> getAiLibraryByLibId(int libraryId);
 
+    @Select("select * from ai_library as lib left join lib_item as item on item.libraryId = lib.libraryId  where itemId = #{itemId}")
+    List<AiLib> getAiLibraryFormItemId(int itemId);
+
     @Select("select * from ai_library where libraryId = #{libraryId} and arithmetic = #{arithmetic}")
     List<AiLib> findLibByAiTypeAndLibraryId(int libraryId,int arithmetic);
 
@@ -107,16 +113,41 @@ public interface HfyDevAiMapper {
     @Update(value = {"<script>" +
             "update ai_library" +
             " set" +
-            " version=${version}"+
+            " version='${version}'"+
             " <if test=\"libraryName != null\">, libraryName='${libraryName}'</if>" +
             " <if test=\"total != null\">, total='${total}'</if>" +
             " where libraryId = '${libraryId}'" +
             "</script>" })
     int updateAiLibrary(AiLib aiLib);
 
+    @Update(value = {"<script>" +
+            "update lib_item " +
+            "<set>" +
+            "<if test=\"_itemName != null and _itemName != ''\"> itemName='#{_itemName}',</if>" +
+            "<if test=\"_itemNo != null and _itemNo != ''\"> itemNo='#{_itemNo}',</if>" +
+            "<if test=\"_idCard != null and _idCard != ''\"> idCard='#{_idCard}',</if>" +
+            "<if test=\"_carNo != null and _carNo != ''\"> carNo='#{_carNo}',</if>" +
+            "<if test=\"_imageUrl != null and _imageUrl != ''\"> imageUrl=#{_imageUrl},</if>" +
+            "trait=#{_trait}" +
+            "</set>" +
+            "<where>" +
+            " itemId = #{itemId}" +
+            "</where>" +
+            "</script>"})
+    int updateAiLibItem(int itemId,
+                        String _itemName,
+                        String _itemNo,
+                        String _idCard,
+                        String _carNo,
+                        String _imageUrl,
+                        String _trait);
+
     @Select("select * from lib_item where libraryId = #{libraryId}")
     List<AiConfig> getLibItemByLibraryId(int libraryId);
 
+    @Select("select version from ai_library where libraryId = #{libId}")
+    List<AiLib> getAiLibVersion(String libId);
+
     @Select("select * from lib_item where itemId = #{itemId}")
     List<AiLib> queryItemInfoByItemId(int itemId);
 
@@ -142,5 +173,97 @@ public interface HfyDevAiMapper {
     int addLibItem(AiLib aiLib);
 
 
+    @Insert("insert into ai_alarm(" +
+            "deviceId," +
+            "channelId," +
+            "arithmetic," +
+            "infoNum," +
+            "createTime," +
+            "mediaPath," +
+            "firmware_version," +
+            "devTime," +
+            "battery," +
+            "`signal`," +
+            "`temp_env`," +
+            "`temp_cpu`," +
+            "ccid" +
+            ") values (" +
+            "#{deviceId}," +
+            "#{channelId}," +
+            "#{arithmetic}," +
+            "#{infoNum}," +
+            "#{unixTimestamp}," +
+            "#{filePaths}," +
+            "#{firmware_version}," +
+            "#{timestamp}," +
+            "#{battery}," +
+            "#{signal}," +
+            "#{temp_env}," +
+            "#{temp_cpu}," +
+            "#{ccid}" +
+            ")")
+    int saveAiAlarm(String deviceId,String channelId,String arithmetic, int infoNum, String unixTimestamp,String filePaths,
+                    String firmware_version,String timestamp,String battery,String signal,String temp_env,String temp_cpu,String ccid);
+
+    @Select("select * from ai_alarm where" +
+            " deviceId = #{deviceId}" +
+            " and channelId = #{channelId}" +
+            " and arithmetic = #{arithmetic}" +
+            " and infoNum = #{infoNum}" +
+            " and createTime = #{unixTimestamp}" +
+            " and mediaPath = #{filePaths}")
+    List<AiAlarm> findInsertAiAlarm(String deviceId,String channelId,String arithmetic, int infoNum, String unixTimestamp,String filePaths);
+
+    @Insert(
+            "insert into ai_alarm_item(" +
+            "alarmId," +
+            "score," +
+            "x1," +
+            "x2," +
+            "y1," +
+            "y2," +
+            "info," +
+            "trait," +
+            "uid" +
+            ") values " +
+            "(" +
+            "#{alarmId}," +
+            "#{score}," +
+            "#{x1}," +
+            "#{x2}," +
+            "#{y1}," +
+            "#{y2}," +
+            "#{info}," +
+            "#{trait}," +
+            "#{uid}" +
+            ")"
+            )
+
+    int inertAiarmItem(int alarmId,String score,String x1,String x2,String y1,String y2,String info,String trait,String uid);
+
+
+    @Select("<script>" +
+            "select * from ai_alarm " +
+            "<where>" +
+            "<if test='startTime != null'  >" +
+            " createTime &gt;= #{startTime}" +
+            "</if>" +
+            "<if test='endTime != null' >" +
+                " and createTime &lt;= #{endTime}" +
+            "</if>" +
+            "</where>" +
+            "ORDER BY ${sort} ${order}" +
+            "</script>")
+    List<AiAlarmData> getAiAlarms(int startTime, int endTime, String order , String sort);
+
+    @Select("select * from ai_alarm" +
+            " where alarmId = #{alarmId}")
+    List<AiAlarmData> getAiAlarmData(String alarmId);
+
+    @Select("select * from ai_alarm_item" +
+            " where alarmId = #{alarmId}")
+    List<AiAlarm> getAiAlarmItemsByAlarmId(String alarmId);
 
+    @Update("update ai_alarm set readState = #{readState} where alarmId = #{alarmId}")
+    int changeAlarmState(String alarmId,int readState);
 }

+ 91 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/AiControlStorageImpl.java

@@ -0,0 +1,91 @@
+package com.genersoft.iot.vmp.storager.impl;
+
+import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.storager.IAiControlStorage;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.dao.HfyDevAiMapper;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarm;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarmData;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+@SuppressWarnings("rawtypes")
+@Component
+public class AiControlStorageImpl implements IAiControlStorage {
+
+    private final Logger logger = LoggerFactory.getLogger(VideoManagerStorageImpl.class);
+    @Autowired
+    private HfyDevAiMapper HfyDevAiMapper;
+
+    public PageInfo<AiAlarmData> searchAiAlarm(String startTime, String endTime, String order, String sort, int p, int l){
+        PageHelper.startPage(p, l);
+        // 默认使用id进行desc降序
+        logger.info("search prop:{} order:{}",sort,order);
+        if(sort == "createTime" || sort == "create"){
+            sort = "createTime";
+        }else if(order.contains("operation")){
+            sort = "operationTime";
+        }else if(order.contains("signal")){
+            sort = "signal";
+        }else{
+            sort = "alarmId";
+        }
+        if(order.contains("asc")){
+            order = "asc";
+        }else if(order.contains("desc")){
+            order = "desc";
+        }
+        logger.info("search prop:{} order:{}",sort,order);
+        // 确定开始时间
+        if(endTime == null || endTime.isEmpty()){endTime = String.valueOf((new Date().getTime()/1000) + 1000);}
+        if(startTime == null || startTime.isEmpty()){startTime = "0";}
+        int _endTime = Integer.parseInt(endTime),
+                _startTime = Integer.parseInt(startTime);
+        logger.info("查询时间 start={} end={}" ,_startTime, _endTime);
+        // 查表
+        List<AiAlarmData> result = HfyDevAiMapper.getAiAlarms(_startTime,_endTime,order ,sort);
+        return new PageInfo<>(result);
+    }
+
+    public AiAlarmData getAlarmData(String alarmId){
+        List<AiAlarmData> alarmList = HfyDevAiMapper.getAiAlarmData(alarmId);
+        if(alarmList.isEmpty()){
+            // todo 中止程序
+            throw new ControllerException(ErrorCode.ERROR400.getCode(),"无法找到告警数据");
+        }
+        AiAlarmData alarmData = alarmList.stream().findFirst().get();
+        List <AiAlarm> alarmItems = HfyDevAiMapper.getAiAlarmItemsByAlarmId(alarmId);
+        alarmData.setItems(alarmItems);
+        return alarmData;
+    }
+
+    public int changeAiAlarm(String alarmId,String cmder){
+        int stateCode = 0;
+        if(cmder == "read"){
+            // 已读
+            stateCode = 1;
+        }else if(cmder == "ignore"){
+            // 未读
+            stateCode = 2;
+        }else{
+            return -1;
+        }
+        List<AiAlarmData> alarmList = HfyDevAiMapper.getAiAlarmData(alarmId);
+        if(alarmList.isEmpty()){
+            return -1;
+        }
+        HfyDevAiMapper.changeAlarmState(alarmId,stateCode);
+
+        return 0;
+    }
+
+
+}

+ 108 - 11
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -1,6 +1,5 @@
 package com.genersoft.iot.vmp.storager.impl;
 
-import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
@@ -8,7 +7,6 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
 import com.genersoft.iot.vmp.service.IGbStreamService;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -16,6 +14,7 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.storager.dao.*;
 import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarm;
 import com.genersoft.iot.vmp.vmanager.bean.AiLib;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.UploadService;
@@ -32,7 +31,6 @@ import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.*;
@@ -305,18 +303,18 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		}
 		AiLib changeAiLibData = new AiLib();
 		changeAiLibData.setLibraryId(libraryId);
-		logger.info("测试测试测试2");
+//		logger.info("测试测试测试2");
 		if(aiLib.getVersion() == "" ){
-			logger.info("设置ai");
+//			logger.info("设置ai");
 			changeAiLibData.setVersion(aiLib.getVersion());
 		}else{
-			logger.info("设置数据库");
+//			logger.info("设置数据库");
 			changeAiLibData.setVersion(libData.getVersion());
 		}
-		logger.info("测试测试测试3");
+//		logger.info("测试测试测试3");
 		if(aiLib.getTotal() != 0){changeAiLibData.setTotal(aiLib.getTotal());}
 		if(aiLib.getLibraryName() != ""){changeAiLibData.setLibraryName(aiLib.getLibraryName());}
-		logger.info("测试测试测试4");
+//		logger.info("测试测试测试4");
 		return HfyDevAiMapper.updateAiLibrary(changeAiLibData);
 	}
 	/**
@@ -339,12 +337,13 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		String carNo = "";
 		int itemType = 2;
 		AiLib aiLib = new AiLib();
+		int newTotal = libData.getTotal();
 		if (arithmetic == 1 && !file.isEmpty()) {
 			// 人脸照片存储
 			UploadService uploadService = new UploadService();
 			fileName = uploadService.uploadLibImg(
 					String.valueOf(libData.getLibraryId()),
-					String.valueOf(libData.getTotal()),
+					String.valueOf(newTotal),
 					file);
 			itemType = 1;
 			idCard = card;
@@ -364,11 +363,62 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		// 添加完成,修改lib的子项
 		AiLib newAiLib = new AiLib();
 		newAiLib.setLibraryId(libraryId);
-		newAiLib.setTotal(libData.getTotal()+1);
-		newAiLib.setVersion(""+(libData.getTotal()+1));
+		newAiLib.setTotal(newTotal+1);
+		newAiLib.setVersion("v_"+(newTotal+1));
 		HfyDevAiMapper.updateAiLibrary(newAiLib);
+		// todo 发送ai库更新命令
+
 		return fileName;
 	}
+
+	public String editLibItem(int itemId, String itemName, String itemNo, String card , MultipartFile file){
+		// 获取item信息
+		List<AiLib> aiLibArr = HfyDevAiMapper.getAiLibraryFormItemId(itemId);
+		if(aiLibArr.isEmpty()){throw new ControllerException(ErrorCode.ERROR400.getCode(),"无法找到lib库");}
+		AiLib libData = aiLibArr.stream().findFirst().get();
+		// 获取设备原有值
+		int arithmetic = libData.getArithmetic();
+		String trait = libData.getTrait();
+		String imageUrl = libData.getImageUrl();
+
+		String _itemName = null;
+		String _itemNo = null;
+		String _idCard = null;
+		String _carNo = null;
+		String _trait = null;
+		String _imageUrl = null;
+
+		if (arithmetic == 1) {
+			// 人脸照片存储
+			if(file!=null && !file.isEmpty()){
+				logger.info("imageUrl:'"+imageUrl+"' len:"+imageUrl.length());
+				UploadService uploadService = new UploadService();
+				if(imageUrl != "" && imageUrl != "\'\'" && imageUrl != "\"\"" && imageUrl.length() != 0){
+					logger.info("change image Url");
+					_imageUrl = uploadService.changeImage(imageUrl,file);
+				}else{
+					logger.info("new image Url");
+					_imageUrl = uploadService.uploadLibImg(
+							String.valueOf(libData.getLibraryId()),
+							String.valueOf(libData.getTotal()),
+							file);
+					logger.info("new imageName:"+_imageUrl);
+				}
+				_trait = "";
+			}
+			if(!card.isEmpty()){_idCard = card;}
+		}else if(arithmetic == 3 ){
+			if(!card.isEmpty()){_carNo = card;}
+		}
+		if(itemName!=""){
+			_itemName = itemName;
+		}
+		if(itemNo != "" ){
+			_itemNo = itemNo;
+		}
+		HfyDevAiMapper.updateAiLibItem(itemId,_itemName,_itemNo,_idCard,_carNo,_imageUrl,_trait);
+		return "ok";
+	}
 	public int addAiConfig(String configName,
 						   int arithmetic,
 						   int triggerType,
@@ -423,6 +473,13 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		return HfyDevAiMapper.getLibItemByLibraryId(libraryId);
 	}
 
+	public String queryAiLibVersion(String libId){
+		List<AiLib> aiLibArr = HfyDevAiMapper.getAiLibVersion(libId);
+		if(aiLibArr.isEmpty()){return "";}
+		AiLib libData = aiLibArr.stream().findFirst().get();
+		return libData.getVersion();
+	}
+
 	public AiLib queryItemInfoByItemId(int itemId){
 		List<AiLib> aiLibItemArr = HfyDevAiMapper.queryItemInfoByItemId(itemId);
 		if(aiLibItemArr.isEmpty()){throw new ControllerException(ErrorCode.ERROR400.getCode(),"无法找到该数据,请检查id");}
@@ -499,6 +556,46 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		return new PageInfo<>(all);
 	}
 
+	/**
+	 * 存储告警数据
+	 * @param deviceId
+	 * @param channelId
+	 * @param arithmetic
+	 * @param alarmItems
+	 * @param uploads
+	 * @return
+	 */
+	public int saveAlarm(String deviceId, String channelId, String arithmetic, List<AiAlarm> alarmItems, List<MultipartFile> uploads,
+	String firmware_version,String timestamp,String battery,String signal,String temp_env,String temp_cpu,String ccid){
+		// 1. 转储上传文件
+		UploadService uploadHandle = new UploadService();
+		String filePaths = uploadHandle.saveDevAlarm(sipConfig.getMediaPath(),deviceId,uploads);
+		Date date = new Date();
+		long unixTimestamp = date.getTime()/1000;
+		logger.info("[文件操作] 告警文件存储成功 文件:{}",filePaths);
+		logger.info("[数据库操作] alarmItems:{}",alarmItems);
+		HfyDevAiMapper.saveAiAlarm(deviceId,channelId,arithmetic,alarmItems.size(), String.valueOf(unixTimestamp),filePaths,
+				firmware_version, timestamp, battery,signal,temp_env,temp_cpu,ccid);
+		logger.info("[数据库操作] saveAiAlarm ok");
+		List<AiAlarm> agoAlarms = HfyDevAiMapper.findInsertAiAlarm(deviceId,channelId,arithmetic,alarmItems.size(), String.valueOf(unixTimestamp),filePaths);
+		if(agoAlarms.isEmpty()){logger.info("[数据库操作] 存储的数据文件无法找到{}",filePaths);return 1;}
+		AiAlarm agoAlarm = agoAlarms.stream().findFirst().get();
+		logger.info("[数据库操作] findInsertAiAlarm ok ,alarmId:{}",agoAlarm.getAlarmId());
+
+		// 4. 存储info信息
+		// todo 修改循环单条加入到数据库中.
+		int AlarmId = agoAlarm.getAlarmId();
+		for (int i = 0; i < alarmItems.size(); i++)
+		{
+			AiAlarm alarm = alarmItems.get(i);
+			HfyDevAiMapper.inertAiarmItem(AlarmId, alarm.getSimilarity(),
+					alarm.getX1(),alarm.getX2(),alarm.getY1(),alarm.getY2(),
+					alarm.getInfo(),alarm.getTrackId(),alarm.getUid());
+		}
+		// 返回正常值
+		return 0;
+	}
+
 	/**
 	 * 获取多个设备
 	 *

+ 172 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/AiAlarm.java

@@ -0,0 +1,172 @@
+package com.genersoft.iot.vmp.vmanager.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.List;
+
+public class AiAlarm {
+    @Schema(description = "设备id")
+    private String deviceId;
+    @Schema(description = "通道id")
+    private String channel;
+    @Schema(description = "算法类型")
+    private String arithmetic = "1";
+    @Schema(description = "告警id")
+    private int alarmId;
+    @Schema(description = "相似度")
+    private String similarity = "0";
+    @Schema(description = "x1")
+    private String x1 = "0";
+    @Schema(description = "x2")
+    private String x2 = "0";
+    @Schema(description = "y1")
+    private String y1 = "0";
+    @Schema(description = "y2")
+    private String y2 = "0";
+    @Schema(description = "额外信息")
+    private String info = "0";
+    @Schema(description = "特征码")
+    private String trait = "0";
+
+
+    @Schema(description = "连续id")
+    private String trackId;
+
+
+    @Schema(description = "对应的设置id")
+    private int itemId;
+    @Schema(description = "uid")
+    private String uid;
+
+    public String getUid() {
+        return uid;
+    }
+
+    public void setUid(String uid) {
+        this.uid = uid;
+    }
+
+
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    public void setChannel(String channel) {
+        this.channel = channel;
+    }
+
+    public String getArithmetic() {
+        return arithmetic;
+    }
+
+    public void setArithmetic(String arithmetic) {
+        this.arithmetic = arithmetic;
+    }
+
+    public int getAlarmId() {
+        return alarmId;
+    }
+
+    public void setAlarmId(int alarmId) {
+        this.alarmId = alarmId;
+    }
+
+    public String getSimilarity() {
+        return similarity;
+    }
+
+    public void setSimilarity(String similarity) {
+        this.similarity = similarity;
+    }
+
+    public String getX1() {
+        return x1;
+    }
+
+    public void setX1(String x1) {
+        this.x1 = x1;
+    }
+
+    public String getX2() {
+        return x2;
+    }
+
+    public void setX2(String x2) {
+        this.x2 = x2;
+    }
+
+    public String getY1() {
+        return y1;
+    }
+
+    public void setY1(String y1) {
+        this.y1 = y1;
+    }
+
+    public String getY2() {
+        return y2;
+    }
+
+    public void setY2(String y2) {
+        this.y2 = y2;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+
+    public void setInfo(String info) {
+        this.info = info;
+    }
+
+    public String getTrait() {
+        return trait;
+    }
+
+    public void setTrait(String trait) {
+        this.trait = trait;
+    }
+
+    public int getItemId() {
+        return itemId;
+    }
+
+    public void setItemId(int itemId) {
+        this.itemId = itemId;
+    }
+
+    public String getTrackId() {
+        return trackId;
+    }
+
+    public void setTrackId(String trackId) {
+        this.trackId = trackId;
+    }
+
+    @Override
+    public String toString() {
+        return "AiAlarm{" +
+                "deviceId='" + deviceId + '\'' +
+                ", channel='" + channel + '\'' +
+                ", arithmetic='" + arithmetic + '\'' +
+                ", alarmId=" + alarmId +
+                ", score='" + similarity + '\'' +
+                ", x1='" + x1 + '\'' +
+                ", x2='" + x2 + '\'' +
+                ", y1='" + y1 + '\'' +
+                ", y2='" + y2 + '\'' +
+                ", info='" + info + '\'' +
+                ", trait='" + trait + '\'' +
+                ", itemId=" + itemId +
+                '}';
+    }
+}

+ 246 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/AiAlarmData.java

@@ -0,0 +1,246 @@
+package com.genersoft.iot.vmp.vmanager.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.List;
+
+public class AiAlarmData {
+    @Schema(description = "设备id")
+    private String deviceId;
+    @Schema(description = "通道id")
+    private String channelId;
+    @Schema(description = "算法类型")
+    private String arithmetic = "1";
+    @Schema(description = "告警id")
+    private int alarmId;
+    @Schema(description = "对应的设置id")
+    private int itemId;
+    @Schema(description = "固件版本")
+    private String firmware_version;
+
+    @Schema(description = "创建时间")
+    private String createTime;
+
+    @Schema(description = "电池电压")
+    private String battery;
+
+    @Schema(description = "信号值")
+    private String signal;
+
+    @Schema(description = "环境温度")
+    private String temp_env;
+
+    @Schema(description = "cpu温度")
+    private String temp_cpu;
+
+    @Schema(description = "算法类型,等效 arithmetic")
+    private String triggerType;
+
+    @Schema(description = "icharge")
+    private String icharge;
+    @Schema(description = "iload")
+    private String iload;
+    @Schema(description = "vcharge")
+    private String vcharge;
+    @Schema(description = "焦距,放大倍数")
+    private String zoom_rate;
+
+    @Schema(description = "资源url")
+    private String mediaPath;
+
+    @Schema(description = "报警状态")
+    private int alarmState;
+
+    @Schema(description = "操作时间")
+    private String operationTime;
+
+    @Schema(description = "识别到的数据数量")
+    private int infoNum;
+
+    @Schema(description = "设备ccid")
+    private String ccid;
+
+    @Schema(description = "告警子项")
+    private List<AiAlarm> items;
+
+
+    public String getChannelId() {
+        return channelId;
+    }
+
+    public void setChannelId(String channelId) {
+        this.channelId = channelId;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getMediaPath() {
+        return mediaPath;
+    }
+
+    public void setMediaPath(String mediaPath) {
+        this.mediaPath = mediaPath;
+    }
+
+    public int getAlarmState() {
+        return alarmState;
+    }
+
+    public void setAlarmState(int alarmState) {
+        this.alarmState = alarmState;
+    }
+
+    public String getOperationTime() {
+        return operationTime;
+    }
+
+    public void setOperationTime(String operationTime) {
+        this.operationTime = operationTime;
+    }
+
+    public int getInfoNum() {
+        return infoNum;
+    }
+
+    public void setInfoNum(int infoNum) {
+        this.infoNum = infoNum;
+    }
+
+
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+
+    public String getArithmetic() {
+        return arithmetic;
+    }
+
+    public void setArithmetic(String arithmetic) {
+        this.arithmetic = arithmetic;
+    }
+
+    public int getAlarmId() {
+        return alarmId;
+    }
+
+    public void setAlarmId(int alarmId) {
+        this.alarmId = alarmId;
+    }
+
+    public int getItemId() {
+        return itemId;
+    }
+
+    public void setItemId(int itemId) {
+        this.itemId = itemId;
+    }
+
+    public String getFirmware_version() {
+        return firmware_version;
+    }
+
+    public void setFirmware_version(String firmware_version) {
+        this.firmware_version = firmware_version;
+    }
+
+    public String getBattery() {
+        return battery;
+    }
+
+    public void setBattery(String battery) {
+        this.battery = battery;
+    }
+
+    public String getSignal() {
+        return signal;
+    }
+
+    public void setSignal(String signal) {
+        this.signal = signal;
+    }
+
+    public String getTemp_env() {
+        return temp_env;
+    }
+
+    public void setTemp_env(String temp_env) {
+        this.temp_env = temp_env;
+    }
+
+    public String getTemp_cpu() {
+        return temp_cpu;
+    }
+
+    public void setTemp_cpu(String temp_cpu) {
+        this.temp_cpu = temp_cpu;
+    }
+
+    public String getTriggerType() {
+        return triggerType;
+    }
+
+    public void setTriggerType(String triggerType) {
+        this.triggerType = triggerType;
+    }
+
+    public String getIcharge() {
+        return icharge;
+    }
+
+    public void setIcharge(String icharge) {
+        this.icharge = icharge;
+    }
+
+    public String getIload() {
+        return iload;
+    }
+
+    public void setIload(String iload) {
+        this.iload = iload;
+    }
+
+    public String getVcharge() {
+        return vcharge;
+    }
+
+    public void setVcharge(String vcharge) {
+        this.vcharge = vcharge;
+    }
+
+    public String getZoom_rate() {
+        return zoom_rate;
+    }
+
+    public void setZoom_rate(String zoom_rate) {
+        this.zoom_rate = zoom_rate;
+    }
+
+
+    public String getCcid() {
+        return ccid;
+    }
+
+    public void setCcid(String ccid) {
+        this.ccid = ccid;
+    }
+
+    public List<AiAlarm> getItems() {
+        return items;
+    }
+
+    public void setItems(List<AiAlarm> items) {
+        this.items = items;
+    }
+}

+ 122 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/AiRequestBody.java

@@ -0,0 +1,122 @@
+package com.genersoft.iot.vmp.vmanager.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+public class AiRequestBody {
+    @Schema(description = "环境温度")
+    private String temp_env;
+    @Schema(description = "cpu温度")
+    private String temp_cpu;
+    @Schema(description = "page")
+    private int p;
+    @Schema(description = "limit")
+    private int l;
+    @Schema(description = "排序方式")
+    private String order;
+    @Schema(description = "排序字段")
+    private String sort;
+    @Schema(description = "开始时间")
+    private String startTime;
+    @Schema(description = "结束时间")
+    private String endTime;
+    @Schema(description = "搜索关键字")
+    private String key;
+
+    public String getTemp_env() {
+        return temp_env;
+    }
+
+    public void setTemp_env(String temp_env) {
+        this.temp_env = temp_env;
+    }
+
+    public String getTemp_cpu() {
+        return temp_cpu;
+    }
+
+    public void setTemp_cpu(String temp_cpu) {
+        this.temp_cpu = temp_cpu;
+    }
+
+    public int getP() {
+        return p!=0?p:1;
+    }
+    public int getPage() {
+        return this.getP();
+    }
+    public void setP(int p) {
+        this.p = p;
+    }
+    public void setPage(int p) {
+        this.setP(p);
+    }
+
+    public int getL() {
+        return l!=0?l:20;
+    }
+    public int getLimit() {
+        return this.getL();
+    }
+
+    public void setL(int l) {
+        this.l = l;
+    }
+    public void setLimit(int l) {
+        this.setL(l);
+    }
+
+    public String getOrder() {
+        return order!=null?order:"";
+    }
+
+    public void setOrder(String order) {
+        this.order = order;
+    }
+
+    public String getSort() {
+        return sort!=null?sort:"";
+    }
+
+    public void setSort(String sort) {
+        this.sort = sort;
+    }
+
+    public String getStartTime() {
+        return startTime!=null?startTime:"";
+    }
+
+    public void setStartTime(String startTime) {
+        this.startTime = startTime;
+    }
+
+    public String getEndTime() {
+        return endTime!=null?endTime:"";
+    }
+
+    public void setEndTime(String endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getKey() {
+        return key!=null?key:"";
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    @Override
+    public String toString() {
+        return "AiRequestBody{" +
+                "temp_env='" + temp_env + '\'' +
+                ", temp_cpu='" + temp_cpu + '\'' +
+                ", p=" + p +
+                ", l=" + l +
+                ", order='" + order + '\'' +
+                ", sort='" + sort + '\'' +
+                ", startTime='" + startTime + '\'' +
+                ", endTime='" + endTime + '\'' +
+                ", key='" + key + '\'' +
+                '}';
+    }
+}

+ 272 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/BodyAiAlarm.java

@@ -0,0 +1,272 @@
+package com.genersoft.iot.vmp.vmanager.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+public class BodyAiAlarm {
+    @Schema(description = "设备id")
+    private  String dev_id;
+    @Schema(description = "通道id")
+    private  String channelId;
+    @Schema(description = "算法类型")
+    private  String arithmetic;
+    @Schema(description = "识别结果")
+    private  RecoInfo reco_info;
+
+    @Schema(description = "固件版本")
+    private String firmware_version;
+
+    @Schema(description = "时间戳")
+    private String timestamp;
+
+    @Schema(description = "电池电压")
+    private String battery;
+
+    @Schema(description = "信号值")
+    private String signal;
+
+    @Schema(description = "环境温度")
+    private String temp_env;
+
+    @Schema(description = "cpu温度")
+    private String cpu_env;
+
+    @Schema(description = "算法类型,等效 arithmetic")
+    private String type;
+
+    @Schema(description = "icharge")
+    private String icharge;
+    @Schema(description = "iload")
+    private String iload;
+    @Schema(description = "vcharge")
+    private String vcharge;
+    @Schema(description = "焦距,放大倍数")
+    private String zoom_rate;
+
+    @Schema(description = "设备ccid")
+    private String ccid;
+    @Schema(description = "upload")
+    private  MultipartFile upload;
+    @Schema(description = "upload0")
+    private  MultipartFile upload0;
+    @Schema(description = "upload1")
+    private  MultipartFile upload1;
+    @Schema(description = "upload2")
+    private  MultipartFile upload2;
+    @Schema(description = "upload3")
+    private  MultipartFile upload3;
+    @Schema(description = "upload4")
+    private  MultipartFile upload4;
+    @Schema(description = "upload5")
+    private  MultipartFile upload5;
+    @Schema(description = "upload6")
+    private  MultipartFile upload6;
+
+
+    @Schema(description = "upload7")
+    private  MultipartFile upload7;
+
+    public String getDev_id() {
+        return dev_id;
+    }
+
+    public void setDev_id(String dev_id) {
+        this.dev_id = dev_id;
+    }
+
+    public String getChannelId() {
+        return channelId;
+    }
+
+    public void setChannelId(String channelId) {
+        this.channelId = channelId;
+    }
+
+    public String getArithmetic() {
+        return arithmetic;
+    }
+
+    public void setArithmetic(String arithmetic) {
+        this.arithmetic = arithmetic;
+    }
+
+    public RecoInfo getReco_info() {
+        return reco_info;
+    }
+
+    public void setReco_info(RecoInfo reco_info) {
+        this.reco_info = reco_info;
+    }
+    public String getFirmware_version() {
+        return firmware_version;
+    }
+
+    public void setFirmware_version(String firmware_version) {
+        this.firmware_version = firmware_version;
+    }
+
+    public String getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(String timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getBattery() {
+        return battery;
+    }
+
+    public void setBattery(String battery) {
+        this.battery = battery;
+    }
+
+    public String getSignal() {
+        return signal;
+    }
+
+    public void setSignal(String signal) {
+        this.signal = signal;
+    }
+
+    public String getTemp_env() {
+        return temp_env;
+    }
+
+    public void setTemp_env(String temp_env) {
+        this.temp_env = temp_env;
+    }
+
+    public String getCpu_env() {
+        return cpu_env;
+    }
+
+    public void setCpu_env(String cpu_env) {
+        this.cpu_env = cpu_env;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getIcharge() {
+        return icharge;
+    }
+
+    public void setIcharge(String icharge) {
+        this.icharge = icharge;
+    }
+
+    public String getIload() {
+        return iload;
+    }
+
+    public void setIload(String iload) {
+        this.iload = iload;
+    }
+
+    public String getVcharge() {
+        return vcharge;
+    }
+
+    public void setVcharge(String vcharge) {
+        this.vcharge = vcharge;
+    }
+
+    public String getZoom_rate() {
+        return zoom_rate;
+    }
+
+    public void setZoom_rate(String zoom_rate) {
+        this.zoom_rate = zoom_rate;
+    }
+
+    public String getCcid() {
+        return ccid;
+    }
+
+    public void setCcid(String ccid) {
+        this.ccid = ccid;
+    }
+
+    public MultipartFile getUpload() {
+        return upload;
+    }
+
+    public void setUpload(MultipartFile upload) {
+        this.upload = upload;
+    }
+
+    public MultipartFile getUpload0() {
+        return upload0;
+    }
+
+    public void setUpload0(MultipartFile upload0) {
+        this.upload0 = upload0;
+    }
+
+    public MultipartFile getUpload1() {
+        return upload1;
+    }
+    public void setUpload1(MultipartFile upload1) {
+        this.upload1 = upload1;
+    }
+
+    public MultipartFile getUpload2() {
+
+        return upload2;
+    }
+
+    public void setUpload2(MultipartFile upload2) {
+        this.upload2 = upload2;
+    }
+
+
+    public MultipartFile getUpload3() {
+        return upload3;
+    }
+
+    public void setUpload3(MultipartFile upload3) {
+        this.upload3 = upload3;
+    }
+
+    public MultipartFile getUpload4() {
+        return upload4;
+    }
+
+    public void setUpload4(MultipartFile upload4) {
+        this.upload4 = upload4;
+    }
+
+    public MultipartFile getUpload5() {
+        return upload5;
+    }
+
+    public void setUpload5(MultipartFile upload5) {
+        this.upload5 = upload5;
+    }
+
+    public MultipartFile getUpload6() {
+        return upload6;
+    }
+
+    public void setUpload6(MultipartFile upload6) {
+        this.upload6 = upload6;
+    }
+
+    public MultipartFile getUpload7() {
+        return upload7;
+    }
+
+    public void setUpload7(MultipartFile upload7) {
+        this.upload7 = upload7;
+    }
+
+
+}

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

@@ -1,7 +1,5 @@
 package com.genersoft.iot.vmp.vmanager.bean;
 
-import io.swagger.v3.oas.annotations.media.Schema;
-
 /**
  * 全局错误码
  */
@@ -11,7 +9,9 @@ public enum ErrorCode {
     ERROR400(400, "参数不全或者错误"),
     ERROR403(403, "无权限操作"),
     ERROR401(401, "请登录后重新请求"),
-    ERROR500(500, "系统异常");
+    ERROR500(500, "系统异常"),
+    VERSION_FAIL(9, "请求版本不对");
+//    void ERROR200(200, "系统异常1");
 
     private final int code;
     private final String msg;

+ 19 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/RecoInfo.java

@@ -0,0 +1,19 @@
+package com.genersoft.iot.vmp.vmanager.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.List;
+
+public class RecoInfo {
+    public List<AiAlarm> getResults() {
+        return results;
+    }
+
+    public void setResults(List<AiAlarm> results) {
+        this.results = results;
+    }
+
+    @Schema(description = "识别结果")
+    private List<AiAlarm> results;
+
+}

+ 123 - 6
src/main/java/com/genersoft/iot/vmp/vmanager/bean/UploadService.java

@@ -1,28 +1,131 @@
 package com.genersoft.iot.vmp.vmanager.bean;
 
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.storager.impl.VideoManagerStorageImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.*;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
+
+import static org.aspectj.weaver.tools.cache.SimpleCacheFactory.path;
 
 public class UploadService {
+    private final Logger logger = LoggerFactory.getLogger(VideoManagerStorageImpl.class);
+
     public String upload(String bId,
                          String uId,
                          MultipartFile file) {
         //获得文件保存路径
         String url = getNewFileName(bId, uId, file);
-        saveToLocal(file,url);
+        String filePath = "libImages/" + File.separator + url;
+        saveToLocal(file,filePath);
         return url;
     }
+
+    public static boolean mkDirectory(String path) {
+        File file = null;
+        try {
+            file = new File(path);
+            if (!file.exists()) {
+                return file.mkdirs();
+            }
+            else{
+                return false;
+            }
+        } catch (Exception e) {
+        } finally {
+            file = null;
+        }
+        return false;
+    }
+
     public String uploadLibImg(String libId,
                                String uId,
                                MultipartFile file){
         String fileName = getNewFileName(libId, uId, file);
-        saveToLocal(file,fileName);
+        String filePath = "libImages/" + File.separator + fileName;
+        saveToLocal(file,filePath);
         return fileName;
     }
 
+
+    public String saveDevAlarm(String baseUrl,String deviceId, List<MultipartFile> uploadFiles){
+        // 1. 检查是否有设备对应的文件夹 ,mFile/设备id/日期/f1
+        // 2. 有无目录,创建目录
+        // 3. 根据时间创建文件名 hhmm_ss-0.jpg
+        // 4. 转储文件
+        // 5. 文件路径示例 mFile/设备id/日期/hhmm_ss-0.jpg|hhmm_ss-1.jpg|hhmm_ss-2.jpg
+//        logger.info("saveDevAlarm {}",sipConfig);
+//        logger.info("mediaPath:{}",sipConfig.getMediaPath());
+        SimpleDateFormat sdf = new SimpleDateFormat();// 格式化时间
+        sdf.applyPattern("yyyy-MM-dd");
+        SimpleDateFormat fileSdf = new SimpleDateFormat();// 格式化时间
+        fileSdf.applyPattern("HHmm_ss");
+        Date date = new Date();
+        String nowDayString = sdf.format(date);
+        String nowFileTimeString = fileSdf.format(date);
+        String devPath = baseUrl +"/"+ File.separator + deviceId;
+        String devDayPath = devPath +"/"+ File.separator + nowDayString;
+        File devFile = new File(devPath);
+        File devDayFile = new File(devDayPath);
+        if(!devFile.exists()||!devDayFile.exists()){
+            devDayFile.mkdirs();
+        }
+        String resultFileField = devDayPath +"/"+ File.separator;
+        for (int i = 0; i < uploadFiles.size(); i++) {
+            // 生成对应的文件名
+            MultipartFile file = uploadFiles.get(i);
+            String extName = getFileExt(file);
+            String originalName = file.getOriginalFilename();
+            String fileName = "";
+
+            if(originalName.contains("big")){
+                fileName = nowFileTimeString + "-" + i + "_big" + "." + extName;
+            }else{
+                fileName = nowFileTimeString + "-" + i + "." + extName;
+            }
+            String filePath = devDayPath +"/"+ File.separator + fileName;
+            if(i>=1){
+                // 为1 时解析
+                resultFileField = resultFileField + "|";
+            }
+            resultFileField = resultFileField + fileName;
+            // 转储文件
+            saveToLocal(file,filePath);
+        }
+        return resultFileField;
+    }
+
+    public List<String> parseFilePath(String rawStr){
+        List<String> fileNameList = new ArrayList<String>();
+        // 解析出前缀以及后缀
+        String baseUrl = rawStr.substring(0,rawStr.lastIndexOf("/"));
+        String fileNames = rawStr.substring(rawStr.lastIndexOf("/")+1);
+        String[] temp;
+        String split = "|";  // 指定分割字符
+        temp = fileNames.split(split); // 分割字符串
+        // 普通 for 循环
+        if(temp.length >= 1){
+            fileNameList.add(rawStr);
+        }else{
+            for (int i = 0;i<temp.length;i++){
+                fileNameList.add(baseUrl+"/"+temp[i]);
+            }
+        }
+        return fileNameList;
+    }
+    public String changeImage(String imgName,MultipartFile file){
+        String filePath = "libImages" + File.separator + imgName;
+        saveToLocal(file,filePath);
+        return imgName;
+    }
     private String getNewFileName(String bId, String uId, MultipartFile file) {
         // 获取文件后缀
         String extension = "";
@@ -32,11 +135,20 @@ public class UploadService {
         }
         return "aiImg_"+bId+"_"+uId+"."+extension;
     }
+    private String getFileExt(MultipartFile file){
+        String extension = "";
+        int i = file.getOriginalFilename().lastIndexOf('.');
+        if (i > 0) {
+            extension = file.getOriginalFilename().substring(i+1);
+        }
+        return extension;
+    }
 
-    private void saveToLocal(MultipartFile file,String fileName){//上传到本地
+    private void saveToLocal(MultipartFile file,String filePath){//上传到本地
         try {
+            logger.info("save file to path {} ,file: {}",filePath,file);
             InputStream input = file.getInputStream();
-            OutputStream outputStream = new FileOutputStream("libImages/" + File.separator + fileName);
+            OutputStream outputStream = new FileOutputStream(filePath);
             byte[] b = new byte[4096];
             int count = input.read(b);
             while (count != -1) {
@@ -48,9 +160,14 @@ public class UploadService {
             input.close();
             outputStream.close();
         } catch (FileNotFoundException e) {
-            e.printStackTrace();
+            logger.info("not found path:{} file:{}",filePath,file);
+            //e.printStackTrace();
         } catch (IOException e) {
-            e.printStackTrace();
+            //e.printStackTrace();
         }
     }
+
+
+
+
 }

+ 49 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java

@@ -15,14 +15,38 @@ public class WVPResult<T> {
         this.data = data;
     }
 
+    // todo 返回结果携带搜索结果等页数
+    public WVPResult(int code, String msg, T data, int page, int limit, int total) {
+        this.code = code;
+        this.msg = msg;
+        this.data = data;
+        this.page =  page;
+        this.limit = limit;
+        this.total = total;
+    }
+
 
     @Schema(description = "错误码,0为成功")
     private int code;
+
     @Schema(description = "描述,错误时描述错误原因")
     private String msg;
+
     @Schema(description = "数据")
     private T data;
 
+    @Schema(description = "查询数据量")
+    private int limit;
+
+
+
+    @Schema(description = "查询页数")
+    private int page;
+
+
+
+    @Schema(description = "总条数")
+    private int total;
 
     public static <T> WVPResult<T> success(T t, String msg) {
         return new WVPResult<>(ErrorCode.SUCCESS.getCode(), msg, t);
@@ -63,4 +87,29 @@ public class WVPResult<T> {
     public void setData(T data) {
         this.data = data;
     }
+
+    public int getLimit() {
+        return limit;
+    }
+
+    public void setLimit(int limit) {
+        this.limit = limit;
+    }
+
+    public int getPage() {
+        return page;
+    }
+
+    public void setPage(int page) {
+        this.page =  page;
+    }
+    public int getTotal() {
+        return total;
+    }
+
+    public void setTotal(int total) {
+        this.total =  total;
+    }
+
+
 }

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

@@ -0,0 +1,76 @@
+package com.genersoft.iot.vmp.vmanager.gb28181.aiLib;
+
+import com.genersoft.iot.vmp.storager.IAiControlStorage;
+import com.genersoft.iot.vmp.vmanager.bean.*;
+import com.genersoft.iot.vmp.vmanager.bean.AiAlarm;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import com.github.pagehelper.PageInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@Tag(name = "ai数据接口")
+@CrossOrigin
+@RestController
+@RequestMapping("/ai")
+public class AiApi {
+    private final static Logger logger = LoggerFactory.getLogger(AiControl.class);
+    @Autowired
+    private IAiControlStorage storager;
+
+    @Operation(summary = "获取告警列表")
+    @Parameter(name = "startTime",description = "开始时间")
+    @Parameter(name = "endTime",description = "结束时间")
+    @Parameter(name = "order", description = "降序", required = false)
+    @Parameter(name = "sort", description = "排序字段", required = false)
+    @Parameter(name = "p", description = "page" , required = false)
+    @Parameter(name = "l", description = "limit" , required = false)
+    @PostMapping("/alarms")
+    public WVPResult getAlarms(@RequestBody AiRequestBody req){
+        WVPResult result = new WVPResult<>();
+        String startTime = req.getStartTime(),
+        endTime = req.getEndTime(),
+        order = req.getOrder(),
+        sort = req.getSort();
+        int p = req.getPage(),
+        l=req.getLimit();
+        logger.info("req data is {}",req);
+        PageInfo<AiAlarmData> pageInfo = storager.searchAiAlarm(startTime,endTime,order,sort,p,l);
+        result.setCode(ErrorCode.SUCCESS.getCode());
+        result.setData(pageInfo.getList());
+        result.setTotal((int) pageInfo.getTotal());
+        result.setPage(p);
+        result.setLimit(l);
+        return result;
+    }
+
+    @Operation(summary = "告警详情详细信息")
+    @Parameter(name = "alarmId",description = "告警id")
+    @GetMapping("/alarm/{alarmId}")
+    public WVPResult getAlarmInfo(@PathVariable String alarmId){
+        WVPResult result = new WVPResult<>();
+        AiAlarmData aiAlarmData = storager.getAlarmData(alarmId);
+        result.setCode(ErrorCode.SUCCESS.getCode());
+        result.setData(aiAlarmData);
+        return result;
+    }
+
+
+    @Operation(summary = "告警信息状态改变")
+    @Parameter(name = "alarmId",description = "告警id",required = true)
+    @Parameter(name = "cmder",description = "命令",required = true)
+    public WVPResult changeAiAlarm(@RequestParam String alarmId,@RequestParam String cmder){
+        WVPResult result = new WVPResult<>();
+        int code = storager.changeAiAlarm(alarmId,cmder);
+        if(code == 0){
+            result.setCode(ErrorCode.SUCCESS.getCode());
+        }else{
+            result.setCode(ErrorCode.ERROR400.getCode());
+        }
+        return result;
+    }
+}

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

@@ -1,16 +1,26 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.aiLib;
 
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.vmanager.bean.*;
+import com.github.pagehelper.PageInfo;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 
 @Tag(name = "ai控制项")
@@ -19,7 +29,10 @@ import java.io.InputStream;
 @RequestMapping("/aiLib")
 public class AiControl {
     private final static Logger logger = LoggerFactory.getLogger(AiControl.class);
-
+    @Autowired
+    private IVideoManagerStorage storager;
+    @Autowired
+    SipConfig sipConfig;
     @Operation(summary = "获取lib图片数据")
     @GetMapping("/libImg/{fileName}")
     public void download(HttpServletResponse response,@PathVariable String fileName) throws Exception{
@@ -27,7 +40,7 @@ public class AiControl {
         logger.info("尝试读取文件,fileName:" + fileName);
         File file = new File(basePath+ File.separator +fileName);
         if (!file.exists() || !file.isFile()){
-            logger.info("文件不存在,fileName:" + fileName);
+            logger.info("文件不存在,fileName:" + fileName +"  filePath: "+basePath+ File.separator +fileName);
             return;
         }
         InputStream inputStream = new FileInputStream(basePath+ File.separator +fileName);
@@ -45,4 +58,148 @@ public class AiControl {
             inputStream.close();
         }
     }
+
+    @Operation(summary = "获取用户上传的文件目录")
+    @GetMapping("/mFile/{devId}/{day}/{fileName}")
+    public void loadMediaImage(HttpServletResponse response,@PathVariable String devId,@PathVariable String day,@PathVariable String fileName) throws Exception{
+        String basePath = sipConfig.getMediaPath();
+        logger.info("尝试读取文件,fileName:" + fileName);
+        String filePath = basePath + File.separator + devId + File.separator + day + File.separator +fileName;
+        File file = new File(filePath);
+        if (!file.exists() || !file.isFile()){
+            logger.info("文件不存在,fileName:" + fileName +"  filePath: "+filePath);
+            return;
+        }
+        InputStream inputStream = new FileInputStream(filePath);
+        response.reset();
+        response.setContentType("application/octet-stream");
+        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+        ServletOutputStream outputStream = response.getOutputStream();
+        byte[] b = new byte[1024];
+        int len;
+        try {
+            while((len = inputStream.read(b)) > 0) {
+                outputStream.write(b, 0, len);
+            }
+        } finally {
+            inputStream.close();
+        }
+    }
+
+    @Operation(summary = "lib库数据")
+    @Parameter(name = "libId", description = "libId", required = true)
+    @Parameter(name = "v", description = "version", required = false)
+    @Parameter(name = "p", description = "page" , required = false)
+    @Parameter(name = "l", description = "limit" , required = false)
+    @GetMapping("/list")
+    public WVPResult getLibList(
+            @RequestParam String libId,
+            @RequestParam String v,
+            @RequestParam(value="p",required = false,defaultValue = "1")int p,
+            @RequestParam(value="l",required = false,defaultValue = "20")int l){
+        // 获取数据库版本
+        if (logger.isDebugEnabled()) {
+            logger.debug("获取AI数据库,子项信息");
+        }
+        WVPResult result = new WVPResult<>();
+        if(libId.isEmpty()){
+            result.setCode(ErrorCode.ERROR400.getCode());
+            result.setMsg("libId is required");
+            return result;
+        }
+
+        // 获取该数据库最新版本
+        String libVersion = storager.queryAiLibVersion(libId);
+        if(Objects.equals(libVersion, v)){
+            try{
+                int _N_libId = Integer.parseInt(libId);
+                PageInfo<AiLib> aiLibArr = storager.searchAiLibItems(p,l,_N_libId,null);
+                result.setCode(ErrorCode.SUCCESS.getCode());
+                result.setMsg(ErrorCode.SUCCESS.getMsg());
+                // xxx 直接返回数组
+                result.setData(aiLibArr.getList());
+                result.setLimit(l);
+                result.setPage(p);
+                result.setTotal((int) aiLibArr.getTotal());
+            }
+            catch (NumberFormatException ex){
+                ex.printStackTrace();
+                result.setCode(ErrorCode.ERROR500.getCode());
+                result.setMsg(ErrorCode.ERROR500.getMsg());
+            }
+        }else{
+            result.setCode(ErrorCode.VERSION_FAIL.getCode());
+            result.setMsg("get lib version");
+            result.setData(libVersion);
+        }
+        return result;
+    }
+
+    @Operation(summary = "告警上报")
+    @PostMapping("/alarm")
+    @ResponseBody
+    public WVPResult alarm(BodyAiAlarm bodyAiAlarm){
+        // ps upload 0-8 为兼容性集成,以防多文件上传
+        String deviceId = bodyAiAlarm.getDev_id();
+        String channelId = bodyAiAlarm.getChannelId();
+        String arithmetic = bodyAiAlarm.getType();
+        List<AiAlarm> items = bodyAiAlarm.getReco_info().getResults();
+        String firmware_version = bodyAiAlarm.getFirmware_version();
+        String timestamp = bodyAiAlarm.getTimestamp();
+        String battery = bodyAiAlarm.getBattery();
+        String signal = bodyAiAlarm.getSignal();
+        String temp_env = bodyAiAlarm.getTemp_env();
+        String temp_cpu = bodyAiAlarm.getCpu_env();
+        String ccid = bodyAiAlarm.getCcid();
+        MultipartFile upload = bodyAiAlarm.getUpload(),
+                upload0 = bodyAiAlarm.getUpload0(),
+                upload1 = bodyAiAlarm.getUpload1(),
+                upload2 = bodyAiAlarm.getUpload2(),
+                upload3 = bodyAiAlarm.getUpload3(),
+                upload4 = bodyAiAlarm.getUpload4(),
+                upload5 = bodyAiAlarm.getUpload5(),
+                upload6 = bodyAiAlarm.getUpload6(),
+                upload7 = bodyAiAlarm.getUpload7();
+
+        logger.debug("上报ai识别接口调用 body:{}",bodyAiAlarm);
+        logger.debug("上报ai识别接口调用 items:{}",items);
+        logger.info("test upload{},upload0{}",upload,upload0);
+        WVPResult result = new WVPResult<>();
+        // 处理上传文件参数,合并 不同upload字段下的 file 文件
+        List<MultipartFile> uploads = new ArrayList<>();
+        hbUploads(upload, upload0, upload1, upload2, uploads);
+        hbUploads(upload3, upload4, upload5, upload6, uploads);
+        if(upload7!=null&&!upload7.isEmpty()){ uploads.add(upload7); }
+        if (deviceId==null||deviceId.isEmpty() || deviceId==""){
+            return WVPResult.fail(ErrorCode.ERROR400.getCode(),"没有设备id");
+        }
+        channelId = channelId.isEmpty()?"":channelId;
+        if(uploads.size() == 0){
+            result.setCode(ErrorCode.ERROR400.getCode());
+            result.setMsg("未接收到上传文件.待处理");
+        }else{
+            // todo 制作存储预警信息的 controller
+            storager.saveAlarm(deviceId,channelId,arithmetic, items,uploads,
+                    firmware_version,timestamp,battery,signal,temp_env,temp_cpu,ccid);
+            result.setCode(ErrorCode.SUCCESS.getCode());
+            result.setMsg("ok");
+        }
+        return result;
+    }
+
+    /**
+     * 合并upload文件. 处理为统一list
+     * @param upload
+     * @param upload0
+     * @param upload1
+     * @param upload2
+     * @param uploads
+     */
+    public void hbUploads(@RequestParam(value = "upload", required = false) MultipartFile upload, @RequestParam(value = "upload0", required = false) MultipartFile upload0, @RequestParam(value = "upload1", required = false) MultipartFile upload1, @RequestParam(value = "upload2", required = false) MultipartFile upload2, List<MultipartFile> uploads) {
+        if(upload!=null&&!upload.isEmpty()){ uploads.add(upload); }
+        if(upload0!=null&&!upload0.isEmpty()){ uploads.add(upload0); }
+        if(upload1!=null&&!upload1.isEmpty()){ uploads.add(upload1); }
+        if(upload2!=null&&!upload2.isEmpty()){ uploads.add(upload2); }
+    }
+    // todo ai库特征码上传
 }

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

@@ -146,6 +146,7 @@ public class DeviceConfig {
 		return storager.addAiLib(aiLib.getArithmetic(),aiLib.getLibraryName());
 	}
 
+
 	@Operation(summary = "编辑ai库")
 	@Parameter(name = "libraryId", description = "libId" ,required = true)
 	@Parameter(name = "aiLib" ,description = "要修改的数据",required = true)

+ 25 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java

@@ -144,6 +144,31 @@ public class DeviceQuery {
 		return storager.addLibItem(libraryId,itemName,itemNo,card,file);
 	}
 
+	@Operation(summary = "编辑lib元素")
+	@Parameter(name = "itemId", description = "lib库id", required = true)
+	@Parameter(name = "itemName", description = "名称,用户名称或者车辆名称",required = false)
+	@Parameter(name = "itemNo", description = "标记号,用于车牌或者工号",required = false)
+	@Parameter(name = "img", description = "图像数据")
+	@Parameter(name = "idCard", description = "身份证号",required = false)
+	@Parameter(name = "carNo", description = "车牌号",required = false)
+	@PostMapping("/editLibItem/{itemId}")
+	public String editLibItem(
+			@PathVariable int itemId,
+			@RequestParam(value="img", required = false) MultipartFile file,
+			@RequestParam(value="itemName", required = false) String itemName,
+			@RequestParam(value="itemNo", required = false) String itemNo,
+			@RequestParam(value="idCard", required = false)  String idCard,
+			@RequestParam(value="carNo", required = false)  String carNo
+	){
+		if (logger.isDebugEnabled()) {
+			logger.debug("新增算法库成员");
+		}
+		String card = "";
+		if(idCard != null){card = idCard;}
+		else if(carNo != null){card = carNo;}
+		return storager.editLibItem(itemId,itemName,itemNo,card,file);
+	}
+
 	@Operation(summary = "ai数据库查询")
 	@GetMapping("/aiLibrary")
 	public List<AiLib> aiLibrary(){
@@ -223,7 +248,6 @@ public class DeviceQuery {
 	@Parameter(name = "count", description = "每页查询数量", required = true)
 	@GetMapping("/devices")
 	public PageInfo<Device> devices(int page, int count){
-		
 		return storager.queryVideoDeviceList(page, count);
 	}
 

+ 17 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java

@@ -111,6 +111,23 @@ public class PtzController {
 		}
 	}
 
+	@Operation(summary = "聚焦")
+	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
+	@Parameter(name = "channelId", description = "通道国标编号", required = true)
+	@PostMapping("/focus/{deviceId}/{channelId}")
+	public void focus(@PathVariable String deviceId,@PathVariable String channelId){
+		if (logger.isDebugEnabled()) {
+			logger.debug(String.format("设备云台控制聚焦 API调用,deviceId:%s ,channelId:%s ",deviceId, channelId));
+		}
+		logger.info("控制设备  {} - {} 通道聚焦",deviceId, channelId);
+		Device device = storager.queryVideoDevice(deviceId);
+		try {
+			cmder.ptzFocus(device, channelId);
+		} catch (SipException | InvalidArgumentException | ParseException e) {
+			logger.error("[命令发送失败] 云台控制: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
+	}
 
 	@Operation(summary = "通用前端控制命令")
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)

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

@@ -42,7 +42,7 @@ public class UserController {
     @Autowired
     private IRoleService roleService;
 
-    @GetMapping("/login")
+//    @GetMapping("/login")
     @PostMapping("/login")
     @Operation(summary = "登录")
     @Parameter(name = "username", description = "用户名", required = true)

+ 7 - 3
src/main/resources/application.yml

@@ -115,6 +115,8 @@ sip:
     # keepalliveToOnline: false
     # 是否存储alarm信息
     alarm: false
+    # hfy ai图片存储位置
+    mediaPath: "mFile"
 
 #zlm 默认服务器配置
 media:
@@ -125,9 +127,11 @@ media:
     # [可选] 返回流地址时的ip,置空使用 media.ip
     stream-ip: szgpay.ticp.net
     #stream-ip: 192.168.1.203
+    #stream-ip: 113.88.194.58
     # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip
     sdp-ip: szgpay.ticp.net
     #sdp-ip: 192.168.1.203
+    #sdp-ip: 113.88.194.58
     # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip
     hook-ip:
     # [必须修改] zlm服务器的http.port
@@ -155,9 +159,9 @@ media:
         # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
         enable: true
         # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功
-        port-range: 30000,32000 # 端口范围
+        port-range: 30000,30500 # 端口范围
         # [可选] 国标级联在此范围内选择端口发送媒体流
-        send-port-range: 30000,32000 # 端口范围
+        send-port-range: 30000,30500 # 端口范围
     # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
     record-assist-port: 0
 
@@ -196,6 +200,6 @@ user-settings:
 # 关闭在线文档(生产环境建议关闭)
 springdoc:
     api-docs:
-        enabled: false
+        enabled: true
     swagger-ui:
         enabled: false

+ 24 - 7
src/main/resources/banner.txt

@@ -1,7 +1,24 @@
- ___       __   ___      ___ ________                ________  ________  ________     
-|\  \     |\  \|\  \    /  /|\   __  \              |\   __  \|\   __  \|\   __  \    
-\ \  \    \ \  \ \  \  /  / | \  \|\  \ ____________\ \  \|\  \ \  \|\  \ \  \|\  \   
- \ \  \  __\ \  \ \  \/  / / \ \   ____\\____________\ \   ____\ \   _  _\ \  \\\  \  
-  \ \  \|\__\_\  \ \    / /   \ \  \___\|____________|\ \  \___|\ \  \\  \\ \  \\\  \ 
-   \ \____________\ \__/ /     \ \__\                  \ \__\    \ \__\\ _\\ \_______\
-    \|____________|\|__|/       \|__|                   \|__|     \|__|\|__|\|_______|
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+...............................................................:::::::.................................................................................................
+............................................................:YK":::::'KK::.............................................................................................
+.........................................................../:::lK}+1K]:::1.............................................................................................
+.................%%%+..................!%%%........+1111111~............:%%%%%%%%%%%%%%%%%%%#...%%%...................%%%%...%%Y...................1111................
+.................%%%+..................!%%%...../1111111111111;.......%%%%%%%%%%%%%%%%%%%%%%#...%%%...................%%%%...%%%%%'................1111................
+.................%%%+..................!%%%...1111..........:111+...R%%%........................%%%...................%%%%...%%%I%%%%%]............1/11:...............
+.................%%%+..................!%%%..>/11:............111:..%%%.........................%%%...................%%%%...%%%`..1l%%%%,.........1111................
+.................%%%%%%%%%%%%%%%%%%%%%%%%%%.:111'.............+11>..%%%%%%%%%%%%%%%%%%%%%%%%#...%%%...................%%%%...%%%`.....:i11*%%>.....1111................
+.................%%%+..................!%%%..!111:............+11>..%%%.........................%%%...................%%%!...%%%`.........1111l%`.:1111................
+.................%%%+..................!%%%...:1111:..........+11>..%%%.........................%%%.................%%%%,....%%%`............./11111111................
+.................%%%+..................!%%%.....~1111111111111111>..%%%.........................%%%%%%%%%%%%%%%%%%%%%%W......%%%`................111111................
+.....................................................:..............................................................................................:11................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................
+.......................................................................................................................................................................

+ 2 - 0
src/main/resources/logback-spring-local.xml

@@ -96,6 +96,8 @@
 	<!-- 日志输出级别 -->
 	<root level="INFO">
 		<appender-ref ref="STDOUT" />
+		<appender-ref ref="sipRollingFile">
+		</appender-ref>
 	</root>
 
 	<logger name="wvp" level="debug" additivity="true">

+ 5 - 0
web_src/config/index.js

@@ -22,6 +22,10 @@ module.exports = {
         target: 'http://localhost:29001',
         changeOrigin: true,
       },
+      '/mFile':{
+        target: 'http://localhost:29001',
+        changeOrigin: true,
+      },
       '/static/snap': {
         target: 'http://localhost:29001',
         changeOrigin: true,
@@ -61,6 +65,7 @@ module.exports = {
   build: {
     // Template for index.html
     index: path.resolve(__dirname, '../../src/main/resources/static/index.html'),
+    user: path.resolve(__dirname, '../../src/main/resources/static/index.html'),
 
     // Paths
     assetsRoot: path.resolve(__dirname, '../../src/main/resources/static/'),

+ 1 - 1
web_src/index.html

@@ -3,7 +3,7 @@
   <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <title>国标28181</title>
+    <title>合方圆-国标28181</title>
     <link rel="shortcut icon" href="static/favicon.ico" type="image/x-icon">
     <link rel="stylesheet" type="text/css" href="./static/css/iconfont.css">
     <link rel="stylesheet" type="text/css" href="./static/css/login.css">

+ 32 - 0
web_src/src/apiStore/api_user.js

@@ -0,0 +1,32 @@
+import axios from "@/apiStore/axios";
+import handle from "@/until/handle";
+import querystring from "querystring"
+// 退出登陆
+function logout(context){
+  return new Promise((resolve, reject)=>{
+    axios.get( "/api/user/logout").then(res=>{
+      if(context){
+        context.$router.push('/login').then();
+      }
+      resolve(res);
+    }).catch(err=>{
+      if(context){
+        context.$message.error(`error!!!`);
+      }
+      console.log(err.msg);
+      reject(err);
+    })
+  });
+}
+
+// login
+async function login(context,params){
+  let [err,res] = await handle(axios.post(`/api/user/login`,querystring.stringify(params)));
+  return [err,res];
+}
+
+
+export default {
+  logout,
+  login
+}

+ 47 - 0
web_src/src/apiStore/axionsBefore.js

@@ -0,0 +1,47 @@
+/*
+ * @Description: axios拦截器
+ * @Version:
+ * @Autor: kindring
+ * @Date: 2021-08-31 11:07:49
+ * @LastEditors: kindring
+ * @LastEditTime: 2021-08-31 15:03:03
+ */
+import axios from 'axios'
+import router from '@/router/index.js';
+
+axios.defaults.baseURL = (process.env.NODE_ENV === 'development') ? process.env.BASE_API : "";
+console.log(axios.defaults.baseURL);
+axios.interceptors.request.use(config => {
+    config.headers.withCredentials = true;
+    // App.$message.info('test')
+    config.changeOrigin= true
+    config.credentials= true;
+        config.secure= true
+    return config
+},error =>{
+    return Promise.reject(error)
+})
+
+// 统一处理错误
+axios.interceptors.response.use(response=>{
+    return response;
+},error => {
+    if (error && error.response) {
+        switch (error.response.status) {
+            case 400:
+                console.log('400错误');
+                // 对400错误您的处理\
+                break
+            case 401:
+                console.log('401权限错误');
+                router.push('/login').then();
+                break
+            // 对 401 错误进行处理
+            default:
+                // 如果以上都不是的处理
+                return Promise.reject(error);
+        }
+    }
+})
+
+export default axios

+ 66 - 0
web_src/src/apiStore/axios.js

@@ -0,0 +1,66 @@
+/*
+ * @Description:
+ * @Version:
+ * @Autor: kindring
+ * @Date: 2021-08-31 10:56:44
+ * @LastEditors: kindring
+ * @LastEditTime: 2021-08-31 14:59:41
+ */
+/*
+ * @Description: http请求封装
+ * @Version:
+ * @Autor: kindring
+ * @Date: 2021-08-31 10:56:44
+ * @LastEditors: kindring
+ * @LastEditTime: 2021-08-31 11:07:17
+ */
+import axios from './axionsBefore'
+let header_data = {
+    Authorization: ''
+}
+// axios.get('/')
+export default {
+    get(url,auth = false){
+        if(auth){
+            return axios.get(url,{headers:{Authorization:header_data.Authorization}})
+        }else{
+            return axios.get(url)
+        }
+    },
+    post(url,data,headers)
+    {
+        return axios.post(url,data,headers)
+    },
+    put(url,data,auth = false)
+    {
+        if(auth)
+        {
+            return axios.put(url,data,{headers:{Authorization:header_data.Authorization}})
+        }else
+        {
+            return axios.put(url,data);
+        }
+    },
+    del(url,auth = false)
+    {
+        if(auth)
+        {
+            return axios.delete(url,{headers:{Authorization:header_data.Authorization}})
+        }else
+        {
+            return axios.delete(url);
+        }
+    },
+    uploader(url,file,auth = false)
+    {
+        let param = new FormData();
+        param.append('file',file)
+        if(auth){
+            return axios.post(url,param,{headers:{Authorization:header_data.Authorization}})
+        }else
+        {
+            return axios.post(url,param)
+        }
+    },
+    axios:axios
+}

+ 35 - 0
web_src/src/assets/index.css

@@ -15,7 +15,26 @@
 .space-between{
   justify-content: space-between;
 }
+.page-header{
+  height: 45px;
+  box-sizing: border-box;
+  margin-bottom: 5px;
+  background-color: #FFFFFF;
+  padding: 0.5rem;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+
+.page-title {
+  font-weight: bold;
+  text-align: left;
+}
 
+.page-header-btn {
+  text-align: right;
+}
 .el-dialog{
   display: flex;
   display: -ms-flex; /* 兼容IE */
@@ -42,3 +61,19 @@
   /*隐藏ie和edge中遮罩的滚动条*/
   overflow: hidden;
 }
+.mx-10{
+  margin-left: 10px;
+  margin-right: 10px;
+}
+
+.w-full{
+  width: 100%;
+}
+
+.text-user{
+  color: #ffaf11;
+}
+.text-ornament{
+  color: black;
+  font-weight: bold;
+}

+ 33 - 34
web_src/src/components/Login.vue

@@ -3,7 +3,7 @@
   <div class="limiter">
     <div class="container-login100">
       <div class="wrap-login100">
-					<span class="login100-form-title p-b-26">WVP视频平台</span>
+					<span class="login100-form-title p-b-26">合方圆视频平台</span>
           <span class="login100-form-title p-b-48">
 						<i class="fa fa-video-camera"></i>
 					</span>
@@ -35,6 +35,7 @@
 
 <script>
 import crypto from 'crypto'
+import api_user from "@/apiStore/api_user";
 export default {
   name: 'Login',
   data(){
@@ -59,64 +60,62 @@ export default {
 
   	//登录逻辑
   	login(){
-  		if(this.username!='' && this.password!=''){
+  		if(this.username!=='' && this.password!==''){
   			this.toLogin();
-  		}
+  		}else{
+        this.$message.warning("请输入账号密码");
+      }
   	},
 
   	//登录请求
-  	toLogin(){
+  	async toLogin(){
   		//需要想后端发送的登录参数
   		let loginParam = {
   			username: this.username,
   			password: crypto.createHash('md5').update(this.password, "utf8").digest('hex')
   		}
-      var that = this;
+      let that = this;
       //设置在登录状态
       this.isLoging = true;
       let timeoutTask = setTimeout(()=>{
-        that.$message.error("登录超时");
-        that.isLoging = false;
-      }, 1000)
-
-      this.$axios({
-      	method: 'get',
-        url:"/api/user/login",
-        params: loginParam
-      }).then(function (res) {
-        window.clearTimeout(timeoutTask)
-        console.log(JSON.stringify(res));
-          if (res.data.code === 0 ) {
-            that.$cookies.set("session", {"username": that.username,"roleId":res.data.data.role.id}) ;
-            //登录成功后
-            that.cancelEnterkeyDefaultAction();
-            that.$router.push('/');
-          }else{
-            that.isLoging = false;
-            that.$message({
-                  showClose: true,
-                  message: '登录失败,用户名或密码错误',
-                  type: 'error'
-              });
-          }
-      }).catch(function (error) {
+        this.$message.error("登录超时");
+        this.isLoging = false;
+      }, 2000)
+      let [error,res] = await api_user.login(this,loginParam);
+      this.isLoging = false;
+      if(error){
         console.log(error)
         window.clearTimeout(timeoutTask)
         that.$message.error(error.response.data.msg);
+        return;
+      }
+      window.clearTimeout(timeoutTask)
+      console.log(JSON.stringify(res));
+      if (res.data.code === 0 ) {
+        that.$cookies.set("session", {"username": that.username,"roleId":res.data.data.role.id}) ;
+        //登录成功后
+        that.cancelEnterkeyDefaultAction();
+        await that.$router.push('/');
+      }else{
         that.isLoging = false;
-      });
+        that.$message({
+          showClose: true,
+          message: '登录失败,用户名或密码错误',
+          type: 'error'
+        });
+      }
     },
     setCookie: function (cname, cvalue, exdays) {
-      var d = new Date();
+      let d = new Date();
       d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
-      var expires = "expires=" + d.toUTCString();
+      let 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;
+        let key = window.event.keyCode;
         if (key == 13) {
           return false;
         }

+ 19 - 5
web_src/src/components/aiConfig.vue

@@ -16,7 +16,11 @@
   <el-container v-loading="isLoading" style="min-height: 82vh;flex-direction: column;" >
     <el-card  class="box-card">
       <div slot="header" class="card-header flex space-between align-center" >
-        <span>{{toArithmetic(aiTypes.face).text}}</span>
+        <span>{{toArithmetic(aiTypes.face).text}}
+        <span class="text-ornament">【</span>
+          <span class="text-user">{{arithmetic[aiTypes.face].configName}}</span>
+          <span class="text-ornament">】</span>
+        </span>
 
         <el-button-group>
           <el-button icon="el-icon-edit" @click="showArithmeticDialog(aiTypes.face)">更改配置</el-button>
@@ -68,7 +72,11 @@
 
     <el-card  class="box-card">
       <div slot="header" class="card-header flex space-between align-center" >
-        <span>{{toArithmetic(aiTypes.carPlate).text}}</span>
+        <span>{{toArithmetic(aiTypes.carPlate).text}}
+          <span class="text-ornament">【</span>
+          <span class="text-user">{{arithmetic[aiTypes.carPlate].configName}}</span>
+          <span class="text-ornament">】</span>
+        </span>
 
         <el-button-group>
           <el-button icon="el-icon-edit" @click="showArithmeticDialog(aiTypes.carPlate)">更改配置</el-button>
@@ -118,7 +126,13 @@
 
     <el-card  class="box-card">
       <div slot="header" class="card-header flex space-between align-center" >
-        <span>{{toArithmetic(aiTypes.fire).text}}</span>
+        <span>
+          {{toArithmetic(aiTypes.fire).text}}
+          <span class="text-ornament">【</span>
+          <span class="text-user">{{arithmetic[aiTypes.fire].configName}}</span>
+          <span class="text-ornament">】</span>
+        </span>
+
 
         <el-button-group>
           <el-button icon="el-icon-edit" @click="showArithmeticDialog(aiTypes.fire)">更改配置</el-button>
@@ -251,9 +265,9 @@ export default {
         method: 'get',
         url: '/api/device/query/devAis/' + that.deviceId
       }).then((res) => {
-        // console.log("获取ai数据结果:" + JSON.stringify(res));
+        console.log("获取ai数据结果:" + JSON.stringify(res));
         this.isLoading = false;
-        // console.log(res);
+        console.log(res);
         let data = res.data;
         if(data.code === 0 ){
           data.data.forEach(ai=>{

+ 299 - 0
web_src/src/components/bell.vue

@@ -0,0 +1,299 @@
+<template>
+  <div id="aiBell w-full" style="width:100%;">
+    <div class="page-header">
+      <div class="page-title flex align-center">
+        <el-button icon="el-icon-back" size="mini" style="font-size: 20px; color: #000;" type="text" @click="toBack" ></el-button>
+        <el-divider direction="vertical"></el-divider>
+        预警中心
+      </div>
+      <div class="page-header-btn">
+        <el-tooltip class="item" effect="dark" content="..." placement="left-start">
+          <el-button icon="el-icon-plus" circle size="mini" ></el-button>
+        </el-tooltip>
+      </div>
+    </div>
+    <div class="tab-box" style="width: 100%;">
+      <el-tabs class="tab-switch" v-model="aiTypeVal" @tab-click="handleAiTypeClick">
+        <el-tab-pane v-for="item in aiTypeArr"
+                     :key="'ai-'+item.val"
+                     :label="item.text"
+                     :name="`${item.val}`"/>
+      </el-tabs>
+      <div class="tab-header">
+        <el-input class=" input-with-select" placeholder="输入算法名片段" v-model="key" >
+        <el-select v-model="triggerTypeVal"
+                   style="width: 120px;"
+                   slot="prepend"
+                   @change="getNowTriggerType" placeholder="触发类型">
+          <el-option
+            :key="'trigger-0'"
+            label="全部类型"
+            :value="0">
+          </el-option>
+          <el-option
+            v-for="item in triggerTypeArr"
+            :key="'trigger-'+item.val"
+            :label="item.text"
+            :value="item.val"
+            :disabled="item.disabled">
+          </el-option>
+        </el-select>
+        <el-button slot="append"
+                   style="width: 80px;"
+                   icon="el-icon-search"
+                   type="primary"
+                   :loading="isLoading"
+                   @click="searchClickHandle">搜索</el-button>
+      </el-input>
+      </div>
+      <div class="tab-content">
+        <el-table
+          class="table"
+          style="overflow-y: auto;"
+          :data="tabList"
+          header-row-class-name="table-header"
+          @sort-change="tableSortChange"
+        >
+          <el-table-column prop="alarmId" label="id" :sortable="'alarmId'"></el-table-column>
+          <el-table-column prop="deviceId" label="设备号"></el-table-column>
+          <el-table-column prop="infoNum" label="子项数量" :sortable="'infoNum'"></el-table-column>
+          <el-table-column prop="firmware_version" label="固件版本号"></el-table-column>
+          <el-table-column prop="signal" label="上传时信号值" :sortable="'signal'">
+            <template slot-scope="scope">
+              <el-tooltip class="item" effect="dark" :content="scope.row.signal+' '+signalTransform(scope.row.signal,false)" placement="left-start">
+                <signal-info :signal-text="scope.row.signal"></signal-info>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+          <el-table-column prop="createTime" label="文件上传时间" :sortable="'createTime'">
+            <template slot-scope="scope">
+              <span>{{getTimeText(scope.row.createTime)}}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="alarmState" label="已读状态">
+            <template slot-scope="scope">
+              <span>{{getAlarmState(scope.row.alarmState)}}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" >
+            <template slot-scope="scope">
+              <el-button size="medium" @click="editAlarmHandle(scope.row)">忽略</el-button>
+              <el-button size="medium" type="primary" @click="moreAlarmHandle(scope.row)">
+                查看
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="tab-page">
+        <el-pagination
+          style="float: right"
+          @size-change="handleSizeChange"
+          @current-change="currentChange"
+          :current-page="currentPage"
+          :page-size="count"
+          :page-sizes="[5,10,15, 25, 35, 50]"
+          layout="total, sizes, prev, pager, next"
+          :total="total"
+        ></el-pagination>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script>
+import {triggerTypes,getTriggerTypeArr,getAITypeArr,toAlarmState} from "@/map/ai.js"
+import handle from "@/until/handle";
+import timeUntil from "@/until/time";
+import SignalInfo from "@/components/common/signalInfo";
+import devTool from "@/hfyUntil/devTool";
+let bellSearchSource = null;
+export default {
+  name: "bell",
+  components: {SignalInfo},
+  data(){
+    return {
+      isLoading: false,
+      triggerTypeVal: 0,
+      triggerTypes: triggerTypes,
+      triggerTypeArr: getTriggerTypeArr(),
+      aiTypeVal: "1",
+      aiTypeArr: getAITypeArr(),
+      key: '',
+      searchKey: '',
+      searchOrder: "descending",
+      searchSort: 'alarmId',
+      lastPage: 1,
+      currentPage: 1,
+      count: 15,
+      total: 1,
+      tabList: [],
+
+    }
+  },
+  beforeDestroy() {
+    this.stopQuest();
+  },
+  mounted() {
+    this.searchClickHandle();
+  },
+  methods:{
+    signalTransform:devTool.signalTransform,
+    toBack(){
+      this.$router.back();
+      if (this.deviceId === "" ) {
+        this.beforeUrl = "/"
+      }
+    },
+    getNowTriggerType(){
+
+    },
+    stopQuest(){
+      if(bellSearchSource){
+        bellSearchSource.cancel();
+        bellSearchSource = null;
+      }
+    },
+    getAlarmState(stateCode){
+      let state = toAlarmState(stateCode);
+      return state?state.text:"null";
+    },
+    getTimeText(timeStamp){
+      return timeUntil.dateFormat(
+        timeUntil.timeStamp_to_Date(timeStamp),
+      )
+    },
+
+    async searchData(searchParam){
+      if(bellSearchSource){
+        this.$message.warning("请等待请求完成");
+      }
+      searchParam.aiType = this.aiTypeVal;
+      bellSearchSource = this.$axios.CancelToken.source();
+      this.isLoading = true;
+      let [err,res] = await handle(
+        this.$axios.post('/ai/alarms',
+          searchParam,
+          {
+            cancelToken: bellSearchSource.token
+          })
+      );
+      this.isLoading = false;
+      bellSearchSource = null;
+      if(err){
+        return this.$message.error(err.message);
+      }
+      console.log(res);
+      let result = res.data;
+      if(result.code === 0){
+        this.tabList = result.data;
+        this.total = result.total;
+        this.libList = result.list;
+      }else{
+        this.$message.warning(result.msg);
+      }
+    },
+
+    handleAiTypeClick(tab, event){
+      console.log(tab, event);
+      console.log(this.aiTypeVal);
+      console.log(tab.name);
+      if(this.aiTypeVal !== tab.name){
+        // todo 更新搜索界面
+      }else{
+        // todo
+      }
+    },
+    currentChange: function (val) {
+      this.currentPage = val;
+      this.changePage();
+    },
+    handleSizeChange: function (val) {
+      this.count = val;
+      this.changePage();
+    },
+    changePage(){
+      // 使用当前配置进行搜索
+      let searchParam = {
+        p: this.currentPage,
+        l: this.count,
+        key: this.searchKey,
+        order: this.searchOrder,
+        sort: this.searchSort,
+      }
+      this.searchData(searchParam);
+    },
+    searchClickHandle(){
+      let searchParam = {
+        p: 1,
+        l: this.count,
+        key: this.key,
+      }
+      this.searchKey = this.key;
+      this.searchData(searchParam);
+    },
+    editAlarmHandle(){
+
+    },
+    moreAlarmHandle(row){
+      this.$router.push(`/alarm/${row.alarmId}`)
+    },
+    // 改变排序结构
+    tableSortChange(v,v1){
+      console.log("sort change");
+      console.log(v,v1);
+      let prop = v.prop,
+          order = v.order;
+      // 构建新搜索数据体进行搜索
+      let searchParam = {
+        order: order,
+        sort: prop,
+        p: 1,
+        l: this.count,
+        key: this.key,
+      }
+      this.searchSort = prop;
+      this.searchOrder = order;
+      this.searchData(searchParam);
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page-header{
+  height: 45px;
+  box-sizing: border-box;
+  margin-bottom: 5px;
+}
+.tab-box{
+  width: 100%;
+  height: calc(100vh - 135px);
+  background-color: #fff;
+  border-radius: 0.25rem;
+  padding: 5px;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+.tab-box .tab-switch{
+  height: 45px;
+}
+.tab-box .tab-header{
+  height: 40px;
+  display: flex;
+}
+.tab-box .tab-header > *{
+  height: 40px;
+}
+.tab-box .tab-content{
+  height: calc(100% - 118px);
+  overflow-y: auto;
+}
+.tab-box .tab-content .table{
+  height: 100%;
+}
+.tab-box .tab-page{
+  height: 35px;
+}
+</style>

+ 5 - 2
web_src/src/components/channelList.vue

@@ -33,7 +33,8 @@
     </div>
   </div>
     <ptz-control ref="ptzControl"/>
-  <devicePlayer ref="devicePlayer" ></devicePlayer>
+<!--  <devicePlayer ref="devicePlayer" ></devicePlayer>-->
+    <custom-player ref="devicePlayer"></custom-player>
   <el-container v-loading="isLoging" style="height: 82vh;">
     <el-aside width="auto" style="height: 82vh; background-color: #ffffff; overflow: auto" v-if="showTree" >
       <DeviceTree ref="deviceTree" :device="device" :onlyCatalog="true" :clickEvent="treeNodeClickEvent" ></DeviceTree>
@@ -128,11 +129,13 @@ import uiHeader from '../layout/UiHeader.vue'
 import moment from "moment";
 import DeviceService from "./service/DeviceService";
 import DeviceTree from "./common/DeviceTree";
-import PtzControl from "./dialog/ptzControl";
+import PtzControl from "./dialog/dialogPtzControl";
+import CustomPlayer from "@/components/dialog/customPlayer";
 
 export default {
   name: 'channelList',
   components: {
+    CustomPlayer,
     PtzControl,
     devicePlayer,
     uiHeader,

+ 394 - 0
web_src/src/components/common/ptzControl.vue

@@ -0,0 +1,394 @@
+<template>
+  <div style="display: flex; justify-content: left;">
+    <div class="control-wrapper">
+      <div class="control-btn control-top" @mousedown="ptzCamera('up')" @mouseup="ptzCamera('stop')">
+        <i class="el-icon-caret-top"></i>
+        <div class="control-inner-btn control-inner"></div>
+      </div>
+      <div class="control-btn control-left" @mousedown="ptzCamera('left')" @mouseup="ptzCamera('stop')">
+        <i class="el-icon-caret-left"></i>
+        <div class="control-inner-btn control-inner"></div>
+      </div>
+      <div class="control-btn control-bottom" @mousedown="ptzCamera('down')" @mouseup="ptzCamera('stop')">
+        <i class="el-icon-caret-bottom"></i>
+        <div class="control-inner-btn control-inner"></div>
+      </div>
+      <div class="control-btn control-right" @mousedown="ptzCamera('right')" @mouseup="ptzCamera('stop')">
+        <i class="el-icon-caret-right"></i>
+        <div class="control-inner-btn control-inner"></div>
+      </div>
+      <div class="control-round">
+        <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
+      </div>
+      <!--                放大 -->
+      <div style="position: absolute; left: 7.25rem; top: -1.1rem" @mousedown="ptzCamera('zoomin')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.875rem;"></i></div>
+      <!--                聚焦 -->
+
+      <div style="position: absolute; left: 7.25rem; top: 1.25rem"
+           @click="ptzCameraFocus('focus')" >
+        <i class="el-icon-aim control-zoom-btn" style="font-size: 1.875rem;"></i>
+      </div>
+
+      <!--                缩小 -->
+      <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-out control-zoom-btn"></i></div>
+      <div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
+        <el-slider v-model="controSpeed" :max="255"></el-slider>
+      </div>
+    </div>
+
+    <div class="control-panel">
+      <el-button-group>
+        <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag>
+        <el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number>
+        <el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button>
+        <el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button>
+        <el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button>
+        <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag>
+        <el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
+        <el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button>
+        <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag>
+        <el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
+        <el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button>
+        <el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag>
+        <el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number>
+        <el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button>
+        <el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button>
+        <el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button>
+        <el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button>
+        <el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag>
+        <el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
+        <el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>
+        <el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag>
+        <el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number>
+        <el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button>
+        <el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button>
+        <el-button style="position: absolute; left: 27rem; top: 7rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描</el-button>
+        <el-button style="position: absolute; left: 27rem; top: 9rem; width: 5rem" size="mini" type="danger" icon="el-icon-switch-button" @click="ptzCamera('stop')">停止</el-button>
+      </el-button-group>
+    </div>
+  </div>
+</template>
+
+<script>
+import handle from "@/until/handle";
+
+let queryTimer = null;
+let isZoom = null;
+let sendStopTimer = null;
+let changToLongDownStateTimer = null;
+// 是否长按
+let isLongDown = null;
+export default {
+  name: "ptzControl",
+  props:{
+    deviceId:{require:true},
+    channelId:{require:true}
+  },
+  data(){
+    return {
+      isLoading: false,
+      tabActiveName: 'control',
+      controSpeed: 30,
+      zoomSpeed: 30,
+      presetPos: 1,
+      cruisingSpeed: 100,
+      cruisingTime: 5,
+      cruisingGroup: 0,
+      scanSpeed: 100,
+      scanGroup: 0,
+    }
+  },
+  methods:{
+    timeSendFocus(){
+      queryTimer = setTimeout(async ()=>{
+        await this.ptzCameraFocus();
+        queryTimer = null;
+      },700)
+    },
+    timeSendStop(){
+      console.log('短按自动跟发stop');
+      this.ptzCamera('stop');
+    },
+    async ptzCamera(command){
+      console.log('云台控制:' + command);
+      let isSendFocus = false;
+      let that = this;
+      let isAutoSendStop = false;
+      if(command === 'zoomin' || command === 'zoomout'){
+          isZoom = true;
+          if (queryTimer!=null){
+            // 中止
+            clearTimeout(queryTimer);
+          }
+      }else if(command === 'stop' && isZoom){
+        // 发送待定值
+        isSendFocus = true;
+      }else{
+
+        isZoom = false;
+        // down 发送特定指令值.
+      }
+      clearTimeout(sendStopTimer);
+      sendStopTimer = null;
+
+      // 不连续发送指令
+      if(command !== 'stop'){
+        isLongDown = false;
+        // 非停止指令
+        changToLongDownStateTimer = setTimeout(()=>{
+          isLongDown = true;
+        },1000)
+      }else{
+        // 确保不是自动对焦
+        if(!isLongDown && !isSendFocus){
+          // 短按阻止立即发送end,等待900ms发送end
+          sendStopTimer = setTimeout(()=>{
+            this.timeSendStop();
+          },1200);
+          console.log('进行短按操作');
+          return;
+        }
+        // 清除定时器
+        clearTimeout(changToLongDownStateTimer);
+        changToLongDownStateTimer = null;
+      }
+      let [err,res] = await handle(this.$axios({
+        method: 'post',
+        url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.zoomSpeed
+      }));
+      if(err){
+        console.error(err)}
+      if(isSendFocus){
+        this.timeSendFocus();
+      }
+    },
+    async ptzCameraFocus(){
+      // todo 发送聚焦http指令
+      console.log("摄像头聚焦");
+      let url = `/api/ptz/focus/`
+      url+=`${this.deviceId}/`;
+      url+=`${this.channelId}/`;
+      let [err,res] = await handle(this.$axios({
+        method: 'post',
+        url: url
+      }));
+      if(err){
+        console.error(err)}
+    },
+    presetPosition: function (cmdCode, presetPos) {
+      console.log('预置位控制:' + this.presetPos + ' : 0x' + cmdCode.toString(16));
+      let that = this;
+      this.$axios({
+        method: 'post',
+        url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=0&parameter2=' + presetPos + '&combindCode2=0'
+      }).then(function (res) {});
+    },
+    setSpeedOrTime: function (cmdCode, groupNum, parameter) {
+      let that = this;
+      let parameter2 = parameter % 256;
+      let combindCode2 = Math.floor(parameter / 256) * 16;
+      console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter2.toString(16) + ' 0x' + combindCode2.toString(16));
+      this.$axios({
+        method: 'post',
+        url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter2 + '&combindCode2=' + combindCode2
+      }).then(function (res) {});
+    },
+    setCommand: function (cmdCode, groupNum, parameter) {
+      let that = this;
+      console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter.toString(16) + ' 0x0');
+      this.$axios({
+        method: 'post',
+        url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter + '&combindCode2=0'
+      }).then(function (res) {});
+    },
+  }
+}
+</script>
+
+<style scoped>
+.control-wrapper {
+  position: relative;
+  width: 6.25rem;
+  height: 6.25rem;
+  max-width: 6.25rem;
+  max-height: 6.25rem;
+  border-radius: 100%;
+  margin-top: 1.5rem;
+  margin-left: 0.5rem;
+  float: left;
+}
+
+.control-panel {
+  position: relative;
+  top: 0;
+  left: 5rem;
+  height: 11rem;
+  max-height: 11rem;
+}
+
+.control-btn {
+  display: flex;
+  justify-content: center;
+  position: absolute;
+  width: 44%;
+  height: 44%;
+  border-radius: 5px;
+  border: 1px solid #78aee4;
+  box-sizing: border-box;
+  transition: all 0.3s linear;
+}
+.control-btn:hover {
+  cursor:pointer
+}
+
+.control-btn i {
+  font-size: 20px;
+  color: #78aee4;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.control-btn i:hover {
+  cursor:pointer
+}
+.control-zoom-btn:hover {
+  cursor:pointer
+}
+
+.control-round {
+  position: absolute;
+  top: 21%;
+  left: 21%;
+  width: 58%;
+  height: 58%;
+  background: #fff;
+  border-radius: 100%;
+}
+
+.control-round-inner {
+  position: absolute;
+  left: 13%;
+  top: 13%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 70%;
+  height: 70%;
+  font-size: 40px;
+  color: #78aee4;
+  border: 1px solid #78aee4;
+  border-radius: 100%;
+  transition: all 0.3s linear;
+}
+
+.control-inner-btn {
+  position: absolute;
+  width: 60%;
+  height: 60%;
+  background: #fafafa;
+}
+
+.control-top {
+  top: -8%;
+  left: 27%;
+  transform: rotate(-45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-top i {
+  transform: rotate(45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-top .control-inner {
+  left: -1px;
+  bottom: 0;
+  border-top: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 100% 0 0;
+}
+
+.control-top .fa {
+  transform: rotate(45deg) translateY(-7px);
+}
+
+.control-left {
+  top: 27%;
+  left: -8%;
+  transform: rotate(45deg);
+  border-radius: 5px 0 5px 100%;
+}
+
+.control-left i {
+  transform: rotate(-45deg);
+}
+
+.control-left .control-inner {
+  right: -1px;
+  top: -1px;
+  border-bottom: 1px solid #78aee4;
+  border-left: 1px solid #78aee4;
+  border-radius: 0 0 0 100%;
+}
+
+.control-left .fa {
+  transform: rotate(-45deg) translateX(-7px);
+}
+
+.control-right {
+  top: 27%;
+  right: -8%;
+  transform: rotate(45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-right i {
+  transform: rotate(-45deg);
+}
+
+.control-right .control-inner {
+  left: -1px;
+  bottom: -1px;
+  border-top: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 100% 0 0;
+}
+
+.control-right .fa {
+  transform: rotate(-45deg) translateX(7px);
+}
+
+.control-bottom {
+  left: 27%;
+  bottom: -8%;
+  transform: rotate(45deg);
+  border-radius: 0 5px 100% 5px;
+}
+
+.control-bottom i {
+  transform: rotate(-45deg);
+}
+
+.control-bottom .control-inner {
+  top: -1px;
+  left: -1px;
+  border-bottom: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 0 100% 0;
+}
+
+.control-bottom .fa {
+  transform: rotate(-45deg) translateY(7px);
+}
+.trank {
+  width: 80%;
+  height: 180px;
+  text-align: left;
+  padding: 0 10%;
+  overflow: auto;
+}
+.trankInfo {
+  width: 80%;
+  padding: 0 10%;
+}
+</style>

+ 66 - 0
web_src/src/components/common/signalInfo.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="signal-box">
+    <div :class="`signal-item-box sign0 ${signalLevel >= 0?'show':''}`" ></div>
+    <div :class="`signal-item-box sign1 ${signalLevel >= 1?'show':''}`"></div>
+    <div :class="`signal-item-box sign2 ${signalLevel >= 2?'show':''}`"></div>
+    <div :class="`signal-item-box sign3 ${signalLevel >= 3?'show':''}`"></div>
+    <div :class="`signal-item-box sign4 ${signalLevel >= 4?'show':''}`"></div>
+    <div :class="`signal-item-box sign5 ${signalLevel >= 5?'show':''}`"></div>
+  </div>
+</template>
+
+<script>
+import devTool from "@/hfyUntil/devTool";
+export default {
+  name: "signalInfo",
+  props:['signalText'],
+  data(){
+    return {
+    }
+  },
+  computed:{
+    signalLevel(){
+      return devTool.signalTransform(this.signalText);
+    }
+  }
+}
+</script>
+
+<style scoped>
+.signal-box{
+  width: 100%;
+  height: 100%;
+  min-width: 30px;
+  min-height: 18px;
+  display: flex;
+  align-items: flex-end;
+}
+.signal-box .signal-item-box{
+  width: 5px;
+  margin-left: 1px;
+  background-color: gray;
+}
+.signal-box .sign0{
+  height: 2px;
+}
+.signal-box .sign1{
+  height: 4px;
+}
+.signal-box .sign2{
+  height: 6px;
+}
+.signal-box .sign3{
+  height: 8px;
+}
+.signal-box .sign4{
+  height: 10px;
+}
+.signal-box .sign5{
+  height: 12px;
+}
+.signal-box .show{
+  background-color: #27cc5c;
+}
+
+
+</style>

+ 3 - 4
web_src/src/components/createConfig.vue

@@ -192,6 +192,7 @@ export default {
       let data = res.data;
       if(data.code === 0 ){
         that.aiLibList = data.data;
+        this.getNowAiLib();
       }else{
         console.log(res);
         console.error("未知的返回结果!")
@@ -204,11 +205,9 @@ export default {
       return key?this.aiLibList[key]:false;
     },
     getNowAiLib(){
-      this.nowAiLib = this.aiLibList.filter(aiLib=>toNumber(aiLib.libraryId)===toNumber(this.aiTypeVal));
+      this.nowAiLib = this.aiLibList.filter(aiLib=>toNumber(aiLib.arithmetic)===toNumber(this.aiTypeVal));
       this.libraryId = 0;
       this.triggerType = this.triggerTypes.unlimited;
-      console.log("------");
-      console.log(this.nowAiLib);
     },
     toAddLibrary(){
       this.$router.push('/createLib');
@@ -228,7 +227,7 @@ export default {
       data.resourcePath = this.resourcePath;
       data.uploadUrl = this.uploadUrl;
       data.pushUrl = this.pushUrl;
-      console.log(data);
+      // console.log(data);
       this.isLoading = true;
       let [err,res] = await handle(this.$axios.post('/api/device/config/addAiConfig',data));
       console.log('---------------------')

+ 1 - 1
web_src/src/components/dialog/addArithmetic.vue

@@ -9,7 +9,7 @@
           <div class="config-right">
 
           </div>
-        </div>
+          </div>
       </div>
       <div id="shared2" style=" margin-top: 1rem;" v-else-if="loaded&&!aiConfigs.length">
         无法找到{{aiTypeInfo.text}}的配置文件

+ 435 - 0
web_src/src/components/dialog/customPlayer.vue

@@ -0,0 +1,435 @@
+<template>
+  <div id="customPlayer" v-loading="isLoging">
+    <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()">
+      <div style="width: 100%; height: 100%">
+        <rtc-player ref="webRTC" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
+      </div>
+      <div id="shared" style="text-align: right; margin-top: 1rem;">
+        <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" >
+          <el-tab-pane label="云台控制" name="control" v-if="showPtz">
+            <ptz-control :device-id="deviceId" :channel-id="channelId"></ptz-control>
+          </el-tab-pane>
+          <el-tab-pane label="编码信息" name="codec" v-loading="tracksLoading">
+            <p>
+              无法播放或者没有声音?&nbsp&nbsp&nbsp试一试&nbsp
+              <el-button size="mini" type="primary" v-if="!coverPlaying" @click="coverPlay">转码播放</el-button>
+              <el-button size="mini" type="danger" v-if="coverPlaying" @click="convertStopClick">停止转码</el-button>
+            </p>
+            <div class="trank" >
+              <p v-if="tracksNotLoaded" style="text-align: center;padding-top: 3rem;">暂无数据</p>
+              <div v-for="(item, index) in tracks" style="width: 50%; float: left" loading>
+                <span >流 {{index}}</span>
+                <div class="trankInfo" v-if="item.codec_type == 0">
+                  <p>格式: {{item.codec_id_name}}</p>
+                  <p>类型: 视频</p>
+                  <p>分辨率: {{item.width}} x {{item.height}}</p>
+                  <p>帧率: {{item.fps}}</p>
+                </div>
+                <div class="trankInfo" v-if="item.codec_type == 1">
+                  <p>格式: {{item.codec_id_name}}</p>
+                  <p>类型: 音频</p>
+                  <p>采样位数: {{item.sample_bit}}</p>
+                  <p>采样率: {{item.sample_rate}}</p>
+                </div>
+              </div>
+
+            </div>
+
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import rtcPlayer from '../dialog/rtcPlayer.vue'
+import PtzControl from "@/components/common/ptzControl";
+export default {
+  name: "customPlayer",
+  components: {
+    PtzControl, rtcPlayer,
+  },
+  computed: {
+    getPlayerShared: function () {
+      return {
+        sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
+        sharedIframe: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></iframe>',
+        sharedRtmp: this.videoUrl
+      };
+    }
+  },
+  created() {
+    console.log(this.player)
+    if (Object.keys(this.player).length === 1) {
+      this.activePlayer = Object.keys(this.player)[0]
+    }
+  },
+  data() {
+    return {
+      video: '',
+      videoUrl: '',
+      activePlayer: "webRTC",
+      // 如何你只是用一种播放器,直接注释掉不用的部分即可
+      player: {
+        //jessibuca : ["ws_flv", "wss_flv"],
+        webRTC: ["rtc", "rtc"],
+      },
+      videoHistory: {
+        date: '',
+        searchHistoryResult: [] //媒体流历史记录搜索结果
+      },
+      showVideoDialog: false,
+      streamId: '',
+      app : '',
+      mediaServerId : '',
+      convertKey: '',
+      deviceId: '',
+      channelId: '',
+      tabActiveName: 'control',
+      hasAudio: false,
+      loadingRecords: false,
+      recordsLoading: false,
+      isLoging: false,
+      controSpeed: 30,
+      timeVal: 0,
+      timeMin: 0,
+      timeMax: 1440,
+      presetPos: 1,
+      cruisingSpeed: 100,
+      cruisingTime: 5,
+      cruisingGroup: 0,
+      scanSpeed: 100,
+      scanGroup: 0,
+      tracks: [],
+      coverPlaying:false,
+      tracksLoading: false,
+      recordPlay: "",
+      showPtz: true,
+      showRrecord: true,
+      tracksNotLoaded: false,
+      sliderTime: 0,
+      seekTime: 0,
+      recordStartTime: 0,
+      showTimeText: "00:00:00",
+      streamInfo: null,
+    };
+  },
+  methods: {
+    tabHandleClick: function(tab, event) {
+      console.log(tab)
+      var that = this;
+      that.tracks = [];
+      that.tracksLoading = true;
+      that.tracksNotLoaded = false;
+      if (tab.name === "codec") {
+        this.$axios({
+          method: 'get',
+          url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtsp&app='+ this.app +'&stream='+ this.streamId
+        }).then(function (res) {
+          that.tracksLoading = false;
+          if (res.data.code == 0 && res.data.tracks) {
+            that.tracks = res.data.tracks;
+          }else{
+            that.tracksNotLoaded = true;
+            that.$message({
+              showClose: true,
+              message: '获取编码信息失败,',
+              type: 'warning'
+            });
+          }
+        }).catch(function (e) {});
+      }
+    },
+    videoError: function (e) {
+      console.log("播放器错误:" + JSON.stringify(e));
+    },
+    play: function (streamInfo, hasAudio) {
+      this.streamInfo = streamInfo;
+      this.hasAudio = hasAudio;
+      this.isLoging = false;
+      // this.videoUrl = streamInfo.rtc;
+      this.videoUrl = this.getUrlByStreamInfo();
+      this.streamId = streamInfo.stream;
+      this.app = streamInfo.app;
+      this.mediaServerId = streamInfo.mediaServerId;
+      this.playFromStreamInfo(false, streamInfo)
+    },
+    playFromStreamInfo: function (realHasAudio, streamInfo) {
+      this.showVideoDialog = true;
+      this.hasaudio = realHasAudio && this.hasaudio;
+      this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
+    },
+    openDialog: function (tab, deviceId, channelId, param) {
+      if (this.showVideoDialog) {
+        return;
+      }
+      // this.tabActiveName = tab;
+      this.channelId = channelId;
+      this.deviceId = deviceId;
+      this.streamId = "";
+      this.mediaServerId = "";
+      this.app = "";
+      this.videoUrl = ""
+      if (!!this.$refs[this.activePlayer]) {
+        this.$refs[this.activePlayer].pause();
+      }
+      this.play(param.streamInfo, param.hasAudio)
+      switch (tab) {
+        case "media":
+          this.play(param.streamInfo, param.hasAudio)
+          break;
+        case "record":
+          this.showVideoDialog = true;
+          this.videoHistory.date = param.date;
+          this.queryRecords()
+          break;
+        case "streamPlay":
+          this.tabActiveName = "media";
+          this.showRrecord = false;
+          this.showPtz = false;
+          this.play(param.streamInfo, param.hasAudio)
+          break;
+        case "control":
+          break;
+      }
+    },
+    getUrlByStreamInfo(){
+      console.log(this.streamInfo)
+      if (location.protocol === "https:") {
+        this.videoUrl = this.streamInfo[this.player[this.activePlayer][1]]
+      }else {
+        this.videoUrl = this.streamInfo[this.player[this.activePlayer][0]]
+      }
+      return this.videoUrl;
+
+    },
+    coverPlay: function () {
+      var that = this;
+      this.coverPlaying = true;
+      this.$refs[this.activePlayer].pause()
+      that.$axios({
+        method: 'post',
+        url: '/api/play/convert/' + that.streamId
+      }).then(function (res) {
+        if (res.data.code === 0) {
+          that.convertKey = res.data.key;
+          setTimeout(()=>{
+            that.isLoging = false;
+            that.playFromStreamInfo(false, res.data.data);
+          }, 2000)
+        } else {
+          that.isLoging = false;
+          that.coverPlaying = false;
+          that.$message({
+            showClose: true,
+            message: '转码失败',
+            type: 'error'
+          });
+        }
+      }).catch(function (e) {
+        console.log(e)
+        that.coverPlaying = false;
+        that.$message({
+          showClose: true,
+          message: '播放错误',
+          type: 'error'
+        });
+      });
+    },
+    convertStopClick: function() {
+      this.convertStop(()=>{
+        this.$refs[this.activePlayer].play(this.videoUrl)
+      });
+    },
+  }
+}
+</script>
+
+<style >
+.control-wrapper {
+  position: relative;
+  width: 6.25rem;
+  height: 6.25rem;
+  max-width: 6.25rem;
+  max-height: 6.25rem;
+  border-radius: 100%;
+  margin-top: 1.5rem;
+  margin-left: 0.5rem;
+  float: left;
+}
+
+.control-panel {
+  position: relative;
+  top: 0;
+  left: 5rem;
+  height: 11rem;
+  max-height: 11rem;
+}
+
+.control-btn {
+  display: flex;
+  justify-content: center;
+  position: absolute;
+  width: 44%;
+  height: 44%;
+  border-radius: 5px;
+  border: 1px solid #78aee4;
+  box-sizing: border-box;
+  transition: all 0.3s linear;
+}
+.control-btn:hover {
+  cursor:pointer
+}
+
+.control-btn i {
+  font-size: 20px;
+  color: #78aee4;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.control-btn i:hover {
+  cursor:pointer
+}
+.control-zoom-btn:hover {
+  cursor:pointer
+}
+
+.control-round {
+  position: absolute;
+  top: 21%;
+  left: 21%;
+  width: 58%;
+  height: 58%;
+  background: #fff;
+  border-radius: 100%;
+}
+
+.control-round-inner {
+  position: absolute;
+  left: 13%;
+  top: 13%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 70%;
+  height: 70%;
+  font-size: 40px;
+  color: #78aee4;
+  border: 1px solid #78aee4;
+  border-radius: 100%;
+  transition: all 0.3s linear;
+}
+
+.control-inner-btn {
+  position: absolute;
+  width: 60%;
+  height: 60%;
+  background: #fafafa;
+}
+
+.control-top {
+  top: -8%;
+  left: 27%;
+  transform: rotate(-45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-top i {
+  transform: rotate(45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-top .control-inner {
+  left: -1px;
+  bottom: 0;
+  border-top: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 100% 0 0;
+}
+
+.control-top .fa {
+  transform: rotate(45deg) translateY(-7px);
+}
+
+.control-left {
+  top: 27%;
+  left: -8%;
+  transform: rotate(45deg);
+  border-radius: 5px 0 5px 100%;
+}
+
+.control-left i {
+  transform: rotate(-45deg);
+}
+
+.control-left .control-inner {
+  right: -1px;
+  top: -1px;
+  border-bottom: 1px solid #78aee4;
+  border-left: 1px solid #78aee4;
+  border-radius: 0 0 0 100%;
+}
+
+.control-left .fa {
+  transform: rotate(-45deg) translateX(-7px);
+}
+
+.control-right {
+  top: 27%;
+  right: -8%;
+  transform: rotate(45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-right i {
+  transform: rotate(-45deg);
+}
+
+.control-right .control-inner {
+  left: -1px;
+  bottom: -1px;
+  border-top: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 100% 0 0;
+}
+
+.control-right .fa {
+  transform: rotate(-45deg) translateX(7px);
+}
+
+.control-bottom {
+  left: 27%;
+  bottom: -8%;
+  transform: rotate(45deg);
+  border-radius: 0 5px 100% 5px;
+}
+
+.control-bottom i {
+  transform: rotate(-45deg);
+}
+
+.control-bottom .control-inner {
+  top: -1px;
+  left: -1px;
+  border-bottom: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 0 100% 0;
+}
+
+.control-bottom .fa {
+  transform: rotate(-45deg) translateY(7px);
+}
+.trank {
+  width: 80%;
+  height: 180px;
+  text-align: left;
+  padding: 0 10%;
+  overflow: auto;
+}
+.trankInfo {
+  width: 80%;
+  padding: 0 10%;
+}
+</style>

+ 3 - 59
web_src/src/components/dialog/devicePlayer.vue

@@ -189,65 +189,7 @@
                 </el-tab-pane>
                 <!--遥控界面-->
                 <el-tab-pane label="云台控制" name="control" v-if="showPtz">
-                    <div style="display: flex; justify-content: left;">
-                        <div class="control-wrapper">
-                            <div class="control-btn control-top" @mousedown="ptzCamera('up')" @mouseup="ptzCamera('stop')">
-                                <i class="el-icon-caret-top"></i>
-                                <div class="control-inner-btn control-inner"></div>
-                            </div>
-                            <div class="control-btn control-left" @mousedown="ptzCamera('left')" @mouseup="ptzCamera('stop')">
-                                <i class="el-icon-caret-left"></i>
-                                <div class="control-inner-btn control-inner"></div>
-                            </div>
-                            <div class="control-btn control-bottom" @mousedown="ptzCamera('down')" @mouseup="ptzCamera('stop')">
-                                <i class="el-icon-caret-bottom"></i>
-                                <div class="control-inner-btn control-inner"></div>
-                            </div>
-                            <div class="control-btn control-right" @mousedown="ptzCamera('right')" @mouseup="ptzCamera('stop')">
-                                <i class="el-icon-caret-right"></i>
-                                <div class="control-inner-btn control-inner"></div>
-                            </div>
-                            <div class="control-round">
-                                <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
-                            </div>
-                            <div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera('zoomin')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.875rem;"></i></div>
-                            <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-out control-zoom-btn"></i></div>
-                             <div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
-                                 <el-slider v-model="controSpeed" :max="255"></el-slider>
-                             </div>
-                        </div>
-
-                        <div class="control-panel">
-                            <el-button-group>
-                                <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag>
-                                <el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number>
-                                <el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button>
-                                <el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button>
-                                <el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button>
-                                <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag>
-                                <el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
-                                <el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button>
-                                <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag>
-                                <el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
-                                <el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button>
-                                <el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag>
-                                <el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number>
-                                <el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button>
-                                <el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button>
-                                <el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button>
-                                <el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button>
-                                <el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag>
-                                <el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
-                                <el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>
-                                <el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag>
-                                <el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number>
-                                <el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button>
-                                <el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button>
-                                <el-button style="position: absolute; left: 27rem; top: 7rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描</el-button>
-                                <el-button style="position: absolute; left: 27rem; top: 9rem; width: 5rem" size="mini" type="danger" icon="el-icon-switch-button" @click="ptzCamera('stop')">停止</el-button>
-                            </el-button-group>
-                        </div>
-                    </div>
+                  <ptz-control :device-id="deviceId" :channel-id="channelId"></ptz-control>
                 </el-tab-pane>
                 <el-tab-pane label="编码信息" name="codec" v-loading="tracksLoading">
                     <p>
@@ -290,10 +232,12 @@ import rtcPlayer from '../dialog/rtcPlayer.vue'
 // import player from '../dialog/easyPlayer.vue'
 import jessibucaPlayer from '../common/jessibuca.vue'
 import recordDownload from '../dialog/recordDownload.vue'
+import PtzControl from "@/components/common/ptzControl";
 export default {
     name: 'devicePlayer',
     props: {},
     components: {
+      PtzControl,
         jessibucaPlayer, rtcPlayer, recordDownload,
     },
     computed: {

+ 231 - 0
web_src/src/components/dialog/dialogPtzControl.vue

@@ -0,0 +1,231 @@
+<template>
+  <div id="ptzControl" v-loading="isLoading">
+    <el-dialog title="云台控制" top="0" :close-on-click-modal="false" :visible.sync="showPTZDialog" @close="close()">
+      <div style="width: 100%; height: 100%"></div>
+    <div id="shared" style="text-align: right; margin-top: 1rem;">
+      <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" >
+        <el-tab-pane label="云台控制" name="control">
+          <ptz-control :device-id="deviceId" :channel-id="channelId"></ptz-control>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import PtzControl from "@/components/common/ptzControl";
+export default {
+  name: "dialogPtzControl",
+  components: {PtzControl},
+  data(){
+    return {
+      showPTZDialog: false,
+      isLoading: false,
+      tabActiveName: 'control',
+      deviceId: '',
+      channelId: '',
+    }
+  },
+  methods:{
+    showPtzControl(deviceId,channelId){
+      this.showPTZDialog = true;
+      this.deviceId = deviceId;
+      this.channelId = channelId;
+    },
+    tabHandleClick: function(tab, event) {
+      console.log('tabHandleClick');
+    },
+
+  }
+}
+</script>
+
+<style scoped>
+.control-wrapper {
+  position: relative;
+  width: 6.25rem;
+  height: 6.25rem;
+  max-width: 6.25rem;
+  max-height: 6.25rem;
+  border-radius: 100%;
+  margin-top: 1.5rem;
+  margin-left: 0.5rem;
+  float: left;
+}
+
+.control-panel {
+  position: relative;
+  top: 0;
+  left: 5rem;
+  height: 11rem;
+  max-height: 11rem;
+}
+
+.control-btn {
+  display: flex;
+  justify-content: center;
+  position: absolute;
+  width: 44%;
+  height: 44%;
+  border-radius: 5px;
+  border: 1px solid #78aee4;
+  box-sizing: border-box;
+  transition: all 0.3s linear;
+}
+.control-btn:hover {
+  cursor:pointer
+}
+
+.control-btn i {
+  font-size: 20px;
+  color: #78aee4;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.control-btn i:hover {
+  cursor:pointer
+}
+.control-zoom-btn:hover {
+  cursor:pointer
+}
+
+.control-round {
+  position: absolute;
+  top: 21%;
+  left: 21%;
+  width: 58%;
+  height: 58%;
+  background: #fff;
+  border-radius: 100%;
+}
+
+.control-round-inner {
+  position: absolute;
+  left: 13%;
+  top: 13%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 70%;
+  height: 70%;
+  font-size: 40px;
+  color: #78aee4;
+  border: 1px solid #78aee4;
+  border-radius: 100%;
+  transition: all 0.3s linear;
+}
+
+.control-inner-btn {
+  position: absolute;
+  width: 60%;
+  height: 60%;
+  background: #fafafa;
+}
+
+.control-top {
+  top: -8%;
+  left: 27%;
+  transform: rotate(-45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-top i {
+  transform: rotate(45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-top .control-inner {
+  left: -1px;
+  bottom: 0;
+  border-top: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 100% 0 0;
+}
+
+.control-top .fa {
+  transform: rotate(45deg) translateY(-7px);
+}
+
+.control-left {
+  top: 27%;
+  left: -8%;
+  transform: rotate(45deg);
+  border-radius: 5px 0 5px 100%;
+}
+
+.control-left i {
+  transform: rotate(-45deg);
+}
+
+.control-left .control-inner {
+  right: -1px;
+  top: -1px;
+  border-bottom: 1px solid #78aee4;
+  border-left: 1px solid #78aee4;
+  border-radius: 0 0 0 100%;
+}
+
+.control-left .fa {
+  transform: rotate(-45deg) translateX(-7px);
+}
+
+.control-right {
+  top: 27%;
+  right: -8%;
+  transform: rotate(45deg);
+  border-radius: 5px 100% 5px 0;
+}
+
+.control-right i {
+  transform: rotate(-45deg);
+}
+
+.control-right .control-inner {
+  left: -1px;
+  bottom: -1px;
+  border-top: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 100% 0 0;
+}
+
+.control-right .fa {
+  transform: rotate(-45deg) translateX(7px);
+}
+
+.control-bottom {
+  left: 27%;
+  bottom: -8%;
+  transform: rotate(45deg);
+  border-radius: 0 5px 100% 5px;
+}
+
+.control-bottom i {
+  transform: rotate(-45deg);
+}
+
+.control-bottom .control-inner {
+  top: -1px;
+  left: -1px;
+  border-bottom: 1px solid #78aee4;
+  border-right: 1px solid #78aee4;
+  border-radius: 0 0 100% 0;
+}
+
+.control-bottom .fa {
+  transform: rotate(-45deg) translateY(7px);
+}
+.trank {
+  width: 80%;
+  height: 180px;
+  text-align: left;
+  padding: 0 10%;
+  overflow: auto;
+}
+.trankInfo {
+  width: 80%;
+  padding: 0 10%;
+}
+</style>
+

+ 0 - 328
web_src/src/components/dialog/ptzControl.vue

@@ -1,328 +0,0 @@
-<template>
-  <div id="ptzControl" v-loading="isLoading">
-    <el-dialog title="云台控制" top="0" :close-on-click-modal="false" :visible.sync="showPTZDialog" @close="close()">
-      <div style="width: 100%; height: 100%"></div>
-    <div id="shared" style="text-align: right; margin-top: 1rem;">
-      <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" >
-        <el-tab-pane label="云台控制" name="control">
-            <div style="display: flex; justify-content: left;">
-              <div class="control-wrapper">
-                <div class="control-btn control-top" @mousedown="ptzCamera('up')" @mouseup="ptzCamera('stop')">
-                  <i class="el-icon-caret-top"></i>
-                  <div class="control-inner-btn control-inner"></div>
-                </div>
-                <div class="control-btn control-left" @mousedown="ptzCamera('left')" @mouseup="ptzCamera('stop')">
-                  <i class="el-icon-caret-left"></i>
-                  <div class="control-inner-btn control-inner"></div>
-                </div>
-                <div class="control-btn control-bottom" @mousedown="ptzCamera('down')" @mouseup="ptzCamera('stop')">
-                  <i class="el-icon-caret-bottom"></i>
-                  <div class="control-inner-btn control-inner"></div>
-                </div>
-                <div class="control-btn control-right" @mousedown="ptzCamera('right')" @mouseup="ptzCamera('stop')">
-                  <i class="el-icon-caret-right"></i>
-                  <div class="control-inner-btn control-inner"></div>
-                </div>
-                <div class="control-round">
-                  <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
-                </div>
-                <div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera('zoomin')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.875rem;"></i></div>
-                <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-out control-zoom-btn"></i></div>
-                <div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
-                  <el-slider v-model="controSpeed" :max="255"></el-slider>
-                </div>
-              </div>
-
-              <div class="control-panel">
-                <el-button-group>
-                  <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag>
-                  <el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number>
-                  <el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button>
-                  <el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button>
-                  <el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button>
-                  <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag>
-                  <el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
-                  <el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button>
-                  <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag>
-                  <el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
-                  <el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button>
-                  <el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag>
-                  <el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number>
-                  <el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button>
-                  <el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button>
-                  <el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button>
-                  <el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button>
-                  <el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag>
-                  <el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
-                  <el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>
-                  <el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag>
-                  <el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number>
-                  <el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button>
-                  <el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button>
-                  <el-button style="position: absolute; left: 27rem; top: 7rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描</el-button>
-                  <el-button style="position: absolute; left: 27rem; top: 9rem; width: 5rem" size="mini" type="danger" icon="el-icon-switch-button" @click="ptzCamera('stop')">停止</el-button>
-                </el-button-group>
-              </div>
-            </div>
-        </el-tab-pane>
-      </el-tabs>
-    </div>
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "ptzControl",
-  data(){
-    return {
-      showPTZDialog: false,
-      isLoading: false,
-      tabActiveName: 'control',
-      deviceId: '',
-      channelId: '',
-      controSpeed: 30,
-      zoomSpeed: 30,
-      presetPos: 1,
-      cruisingSpeed: 100,
-      cruisingTime: 5,
-      cruisingGroup: 0,
-      scanSpeed: 100,
-      scanGroup: 0,
-    }
-  },
-  methods:{
-    showPtzControl(deviceId,channelId){
-      this.showPTZDialog = true;
-      this.deviceId = deviceId;
-      this.channelId = channelId;
-    },
-    tabHandleClick: function(tab, event) {
-      console.log('tabHandleClick');
-    },
-    ptzCamera: function (command) {
-      console.log('云台控制:' + command);
-      let that = this;
-      this.$axios({
-        method: 'post',
-        url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.zoomSpeed
-      }).then(function (res) {});
-    },
-    presetPosition: function (cmdCode, presetPos) {
-      console.log('预置位控制:' + this.presetPos + ' : 0x' + cmdCode.toString(16));
-      let that = this;
-      this.$axios({
-        method: 'post',
-        url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=0&parameter2=' + presetPos + '&combindCode2=0'
-      }).then(function (res) {});
-    },
-    setSpeedOrTime: function (cmdCode, groupNum, parameter) {
-      let that = this;
-      let parameter2 = parameter % 256;
-      let combindCode2 = Math.floor(parameter / 256) * 16;
-      console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter2.toString(16) + ' 0x' + combindCode2.toString(16));
-      this.$axios({
-        method: 'post',
-        url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter2 + '&combindCode2=' + combindCode2
-      }).then(function (res) {});
-    },
-    setCommand: function (cmdCode, groupNum, parameter) {
-      let that = this;
-      console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter.toString(16) + ' 0x0');
-      this.$axios({
-        method: 'post',
-        url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter + '&combindCode2=0'
-      }).then(function (res) {});
-    },
-  }
-}
-</script>
-
-<style scoped>
-.control-wrapper {
-  position: relative;
-  width: 6.25rem;
-  height: 6.25rem;
-  max-width: 6.25rem;
-  max-height: 6.25rem;
-  border-radius: 100%;
-  margin-top: 1.5rem;
-  margin-left: 0.5rem;
-  float: left;
-}
-
-.control-panel {
-  position: relative;
-  top: 0;
-  left: 5rem;
-  height: 11rem;
-  max-height: 11rem;
-}
-
-.control-btn {
-  display: flex;
-  justify-content: center;
-  position: absolute;
-  width: 44%;
-  height: 44%;
-  border-radius: 5px;
-  border: 1px solid #78aee4;
-  box-sizing: border-box;
-  transition: all 0.3s linear;
-}
-.control-btn:hover {
-  cursor:pointer
-}
-
-.control-btn i {
-  font-size: 20px;
-  color: #78aee4;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-.control-btn i:hover {
-  cursor:pointer
-}
-.control-zoom-btn:hover {
-  cursor:pointer
-}
-
-.control-round {
-  position: absolute;
-  top: 21%;
-  left: 21%;
-  width: 58%;
-  height: 58%;
-  background: #fff;
-  border-radius: 100%;
-}
-
-.control-round-inner {
-  position: absolute;
-  left: 13%;
-  top: 13%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  width: 70%;
-  height: 70%;
-  font-size: 40px;
-  color: #78aee4;
-  border: 1px solid #78aee4;
-  border-radius: 100%;
-  transition: all 0.3s linear;
-}
-
-.control-inner-btn {
-  position: absolute;
-  width: 60%;
-  height: 60%;
-  background: #fafafa;
-}
-
-.control-top {
-  top: -8%;
-  left: 27%;
-  transform: rotate(-45deg);
-  border-radius: 5px 100% 5px 0;
-}
-
-.control-top i {
-  transform: rotate(45deg);
-  border-radius: 5px 100% 5px 0;
-}
-
-.control-top .control-inner {
-  left: -1px;
-  bottom: 0;
-  border-top: 1px solid #78aee4;
-  border-right: 1px solid #78aee4;
-  border-radius: 0 100% 0 0;
-}
-
-.control-top .fa {
-  transform: rotate(45deg) translateY(-7px);
-}
-
-.control-left {
-  top: 27%;
-  left: -8%;
-  transform: rotate(45deg);
-  border-radius: 5px 0 5px 100%;
-}
-
-.control-left i {
-  transform: rotate(-45deg);
-}
-
-.control-left .control-inner {
-  right: -1px;
-  top: -1px;
-  border-bottom: 1px solid #78aee4;
-  border-left: 1px solid #78aee4;
-  border-radius: 0 0 0 100%;
-}
-
-.control-left .fa {
-  transform: rotate(-45deg) translateX(-7px);
-}
-
-.control-right {
-  top: 27%;
-  right: -8%;
-  transform: rotate(45deg);
-  border-radius: 5px 100% 5px 0;
-}
-
-.control-right i {
-  transform: rotate(-45deg);
-}
-
-.control-right .control-inner {
-  left: -1px;
-  bottom: -1px;
-  border-top: 1px solid #78aee4;
-  border-right: 1px solid #78aee4;
-  border-radius: 0 100% 0 0;
-}
-
-.control-right .fa {
-  transform: rotate(-45deg) translateX(7px);
-}
-
-.control-bottom {
-  left: 27%;
-  bottom: -8%;
-  transform: rotate(45deg);
-  border-radius: 0 5px 100% 5px;
-}
-
-.control-bottom i {
-  transform: rotate(-45deg);
-}
-
-.control-bottom .control-inner {
-  top: -1px;
-  left: -1px;
-  border-bottom: 1px solid #78aee4;
-  border-right: 1px solid #78aee4;
-  border-radius: 0 0 100% 0;
-}
-
-.control-bottom .fa {
-  transform: rotate(-45deg) translateY(7px);
-}
-.trank {
-  width: 80%;
-  height: 180px;
-  text-align: left;
-  padding: 0 10%;
-  overflow: auto;
-}
-.trankInfo {
-  width: 80%;
-  padding: 0 10%;
-}
-</style>
-

+ 41 - 0
web_src/src/components/layoutCom/user_header.vue

@@ -0,0 +1,41 @@
+<template>
+  <div class="u-header">
+    <el-menu router :default-active="activeIndex" menu-trigger="click" background-color="#001529" text-color="#fff"
+             active-text-color="#1890ff" mode="horizontal">
+      <el-menu-item index="/">主控台</el-menu-item>
+      <el-menu-item index="/userInfo">个人信息</el-menu-item>
+      <el-menu-item v-if="editUser" index="/userManager">用户管理</el-menu-item>
+    </el-menu>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "user_header",
+  data(){
+    return {
+      activeIndex: this.$route.path,
+      editUser: this.$cookies.get("session").roleId==1,
+
+    }
+  }
+}
+</script>
+
+<style scoped>
+.u-header{
+  width: 100%;
+  height: 100%;
+  max-height: 100px;
+  box-shadow: 0 0 1px black;
+  display: flex;
+  align-items: center;
+  background-color: rgb(0, 21, 41);
+  justify-content: space-between;
+  position: sticky;
+}
+.u-header .u-chunk{
+  display: flex;
+}
+
+</style>

+ 232 - 0
web_src/src/components/mediaView.vue

@@ -0,0 +1,232 @@
+<template>
+  <div id="mediaView " style="width:100%;">
+    <div class="page-header">
+      <div class="page-title flex align-center">
+        <el-button icon="el-icon-back" size="mini" style="font-size: 20px; color: #000;" type="text" @click="toBack" ></el-button>
+        <el-divider direction="vertical"></el-divider>
+        告警详情
+      </div>
+      <div class="page-header-btn">
+        <el-tooltip class="item" effect="dark" content="..." placement="left-start">
+          <el-button icon="el-icon-plus" circle size="mini" ></el-button>
+        </el-tooltip>
+      </div>
+    </div>
+    <div class="tab-box" style="width: 100%;">
+      <div class="viewBox">
+        <div class="view">
+          <img v-show="mediaImages[mediaInd]" :src="'/aiLib/'+mediaImages[mediaInd].url" alt="">
+        </div>
+        <div class="viewController">
+          <div class="mediaImage"
+               v-for="item in mediaImages"
+               :key="'mediaImage-'+item.ind"
+               @click="selectMedia(item.ind)"
+          >
+            <img :src="'/aiLib/'+item.url" alt="">
+          </div>
+        </div>
+      </div>
+      <div class="infoTab">
+        <div class="infoRow">
+          <div class="label">算法类型</div>
+          <div class="value">{{rowData.arithmetic}}</div>
+        </div>
+        <div class="infoRow">
+          <div class="label">固件版本</div>
+          <div class="value">{{rowData.firmware_version}}</div>
+        </div>
+
+        <div class="infoRow">
+          <div class="label">电池电压</div>
+          <div class="value">{{rowData.battery}}</div>
+        </div>
+
+        <div class="infoRow">
+          <div class="label">信号值</div>
+          <div class="value">{{rowData.signal}}</div>
+        </div>
+        <div class="infoRow">
+          <div class="label">环境温度</div>
+          <div class="value">{{rowData.temp_env}}</div>
+        </div>
+        <div class="infoRow">
+          <div class="label">信号值</div>
+          <div class="value">{{rowData.temp_cpu}}</div>
+        </div>
+        <div class="infoRow">
+          <div class="label">创建时间</div>
+          <div class="value">{{getTimeText(rowData.createTime)}}</div>
+        </div>
+      </div>
+<!--      todo 可能会添加报警列表来进行快速选择-->
+    </div>
+  </div>
+</template>
+
+<script>
+import devTool from "@/hfyUntil/devTool";
+import handle from "@/until/handle";
+import timeUntil from "@/until/time";
+
+export default {
+  name: "mediaView",
+  data(){
+    return {
+      isLoading: false,
+      alarmId: "",
+      rowData: {},
+      mediaImages: [
+      ],
+      mediaInd: 0,
+    }
+  },
+  mounted() {
+    let alarmId = this.$route.params.alarmId;
+    if (!alarmId){
+      return this.toBack();
+    }
+    this.alarmId = alarmId;
+    this.loadAlarm();
+  },
+  methods: {
+    signalTransform: devTool.signalTransform,
+    toBack() {
+      this.$router.back();
+      if (this.alarmId === "") {
+        this.beforeUrl = "/bell"
+      }
+    },
+    async loadAlarm(){
+      this.isLoading = true;
+      let [err,res] = await handle(this.$axios.get(`/ai/alarm/${this.alarmId}`));
+      this.isLoading = false;
+      if(err){
+        this.$message.error('无法找到该预警');
+        // todo 显示错误提示页面
+        return 0;
+      }
+      let resultData= res.data;
+      if(resultData.code === 0){
+        this.rowData = resultData.data;
+        this.transformResponse();
+      }else{
+        console.log(resultData);
+        this.$message.warning(resultData.msg);
+      }
+
+    },
+    transformResponse(){
+      let _data = this.rowData;
+      let mediaPaths = devTool.parseMediaPath(_data.mediaPath);
+      this.mediaImages = mediaPaths.map((url,i)=>{
+        return {
+          url:url,
+          ind: i,
+        }
+      });
+      this.selectMedia(this.mediaImages[0].ind);
+      console.log(this.mediaImages);
+
+    },
+    selectMedia(ind){
+      this.mediaInd = ind;
+    },
+
+    getTimeText(timeStamp){
+      return timeUntil.dateFormat(
+        timeUntil.timeStamp_to_Date(timeStamp),
+      )
+    },
+  }
+}
+</script>
+
+<style scoped>
+
+.tab-box{
+  width: 100%;
+  height: calc(100vh - 135px);
+  background-color: #fff;
+  border-radius: 0.25rem;
+  padding: 5px;
+  box-sizing: border-box;
+  overflow: hidden;
+  display: flex;
+}
+.viewBox{
+  width: 75%;
+  height: 100%;
+  padding-left: 5px;
+  padding-right: 4px;
+  box-sizing: border-box;
+}
+.infoTab{
+  width: 25%;
+  height: 100%;
+  overflow: auto;
+  box-sizing: border-box;
+  border-left: 1px solid lightgray;
+  padding-left: 4px;
+  padding-right: 5px;
+}
+.viewBox .view{
+  height: calc(100% - 150px);
+}
+.viewBox .viewController{
+  height: 150px;
+  display: flex;
+  flex-wrap: wrap;
+  box-sizing: border-box;
+  border-top: 1px solid lightgray;
+}
+
+.viewBox .viewController .mediaImage{
+  width: 140px;
+  height: 140px;
+  border-radius: 0.125rem;
+  border: 1px lightgray solid;
+  box-sizing: border-box;
+  margin-top: 5px;
+  margin-left: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.viewBox .viewController .mediaImage img{
+  width: auto;
+  height: auto;
+  max-width: 100%;
+  max-height: 100%;
+}
+.viewBox .view {
+  width: 100%;
+  height: calc(100% - 150px);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.viewBox .view img{
+  width: auto;
+  height: auto;
+  max-width: 100%;
+  max-height: 100%;
+}
+.infoRow{
+  width: 100%;
+  height: 35px;
+  display: flex;
+  align-items: center;
+  box-sizing: border-box;
+}
+.infoRow .value{
+  box-sizing: border-box;
+  padding: 0 5px;
+}
+.infoRow .label{
+  width: 80px;
+  border-right: 1px solid lightgray;
+}
+
+
+</style>

+ 67 - 0
web_src/src/components/u_page/u_info.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="w-full">
+    <div class="page-header">
+      <div class="page-title flex align-center">
+        <el-button icon="el-icon-back" size="mini" style="font-size: 20px; color: #000;" type="text" @click="toBack" ></el-button>
+        <el-divider direction="vertical"></el-divider>
+        个人信息管理
+      </div>
+      <div class="page-header-btn">
+        <el-button  @click="logout">注销</el-button>
+      </div>
+    </div>
+    <div class="u-container">
+      <div class="u-i-info">
+        <h2>欢迎回来
+          <span class="text-ornament">【</span>
+          <span class="text-user">{{this.$cookies.get("session").username}}</span>
+          <span class="text-ornament">】</span>
+        </h2>
+      </div>
+      <div class="u-i-info">
+        <el-button @click="changePassword">修改密码</el-button>
+      </div>
+    </div>
+    <change-password ref="changePasswordDialog"></change-password>
+  </div>
+</template>
+
+<script>
+import api_user from "@/apiStore/api_user";
+import handle from "@/until/handle";
+import ChangePassword from "@/components/dialog/changePassword";
+
+export default {
+  name: "u_info",
+  components: {ChangePassword},
+  data(){
+    return {}
+  },
+  methods:{
+    toBack(){
+      this.$router.back();
+      // if (this.alarmId === "") {
+      //   this.beforeUrl = "/"
+      // }
+    },
+    async logout() {
+      console.log('----------')
+      await handle(api_user.logout(this))
+    },
+    changePassword() {
+      this.$refs.changePasswordDialog.openDialog()
+    },
+  }
+}
+</script>
+
+<style scoped>
+
+.u-container{
+  background: #fff;
+  width: 100%;
+  text-align: left;
+  padding:  5px;
+  box-sizing: border-box;
+}
+</style>

+ 50 - 0
web_src/src/hfyUntil/devTool.js

@@ -0,0 +1,50 @@
+function signalTransform(signalText,isNum = true){
+  let signal = signalText?signalText.replace("dbm",""):"-110";
+  signal = Math.abs(parseInt(signal));
+  let level = 0;
+  let levelText = "";
+  if(signal<=60){
+    level = 5;
+    levelText = "良好";
+  }else if(signal<=70){
+    level = 4;
+    levelText = "好";
+  }else if(signal<=80){
+    level = 3;
+    levelText = "一般";
+  }else if(signal<=90){
+    level = 2;
+    levelText = "良好";
+  }else if(signal<=100){
+    level = 1;
+    levelText = "差";
+  }else{
+    level = 0;
+    levelText = "极差";
+  }
+  return isNum?level:levelText;
+}
+
+function parseMediaPath(rawStr){
+  let fileNameList = [];
+  // 解析出前缀以及后缀
+  let baseUrl = rawStr.substring(0,rawStr.lastIndexOf("/"));
+  let fileNames = rawStr.substring(rawStr.lastIndexOf("/")+1);
+  let temp = [];
+  let split = "|";  // 指定分割字符
+  temp = fileNames.split(split); // 分割字符串
+  // 普通 for 循环
+  if(temp.length <= 1){
+    fileNameList.push(rawStr);
+  }else{
+    for (let i = 0;i<temp.length;i++){
+      fileNameList.push(baseUrl+"/"+temp[i]);
+    }
+  }
+  return fileNameList;
+}
+
+export default {
+  signalTransform,
+  parseMediaPath
+}

+ 27 - 9
web_src/src/layout/UiHeader.vue

@@ -14,7 +14,13 @@
       <el-menu-item index="/mediaServerManger">节点管理</el-menu-item>
       <el-menu-item index="/parentPlatformList/15/1">国标级联</el-menu-item>
       <el-menu-item index="/aiLib">算法管理</el-menu-item>
-      <el-menu-item v-if="editUser" index="/userManager">用户管理</el-menu-item>
+
+      <el-menu-item index="/bell">
+        <el-tooltip content="告警中心" class="mr-5">
+          <el-icon name="bell" color="#fff"></el-icon>
+        </el-tooltip>
+      </el-menu-item>
+
 
       <!--            <el-submenu index="/setting">-->
       <!--              <template slot="title">系统设置</template>-->
@@ -23,17 +29,21 @@
       <!--              <el-menu-item index="/setting/media">媒体服务</el-menu-item>-->
       <!--            </el-submenu>-->
       <!--            <el-menu-item style="float: right;" @click="loginout">退出</el-menu-item>-->
-      <el-submenu index="" style="float: right;">
-        <template slot="title">欢迎,{{ this.$cookies.get("session").username }}</template>
-        <el-menu-item @click="openDoc">在线文档</el-menu-item>
-        <el-menu-item >
-          <el-switch v-model="alarmNotify" inactive-text="报警信息推送" @change="alarmNotifyChannge"></el-switch>
-        </el-menu-item>
-        <el-menu-item @click="changePassword">修改密码</el-menu-item>
+      <el-submenu index="" style="float: right;display: flex;align-items: center;">
+        <template slot="title" class="flex align-center space-between">
+            <span class="ml-5">欢迎,{{ this.$cookies.get("session").username }}</span>
+        </template>
+<!--        <el-menu-item @click="openDoc">在线文档</el-menu-item>-->
+        <el-menu-item @click="openUserCenter">个人中心</el-menu-item>
+<!--        <el-menu-item >-->
+<!--          <el-switch v-model="alarmNotify" inactive-text="报警信息推送" @change="alarmNotifyChannge"></el-switch>-->
+<!--        </el-menu-item>-->
+<!--        <el-menu-item @click="changePassword">修改密码</el-menu-item>-->
         <el-menu-item @click="loginout">注销</el-menu-item>
       </el-submenu>
+
     </el-menu>
-    <changePasswordDialog ref="changePasswordDialog"></changePasswordDialog>
+
   </div>
 </template>
 
@@ -86,6 +96,9 @@ export default {
       console.log(process.env.BASE_API)
       window.open(!!process.env.BASE_API ? process.env.BASE_API + "/doc.html" : "/doc.html")
     },
+    openUserCenter(){
+      this.$router.push('/userInfo')
+    },
     beforeunloadHandler() {
       this.sseSource.close();
     },
@@ -106,6 +119,8 @@ export default {
             type: 'warning'
           });
           console.log("收到信息:" + evt.data);
+          // 推送消息给其他组件
+
         });
         this.sseSource.addEventListener('open', function (e) {
           console.log("SSE连接打开.");
@@ -146,6 +161,9 @@ export default {
       this.sseSource.close();
     }
   },
+  showBell(){
+    console.log('11122')
+  }
 
 }
 

+ 1 - 16
web_src/src/layout/index.vue

@@ -3,6 +3,7 @@
     <el-header>
       <ui-header/>
     </el-header>
+
     <el-main>
       <el-container>
         <transition name="fade">
@@ -25,23 +26,7 @@ export default {
 </script>
 <style>
 /*定义标题栏*/
-.page-header {
-  background-color: #FFFFFF;
-  margin-bottom: 1rem;
-  padding: 0.5rem;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
 
-.page-title {
-  font-weight: bold;
-  text-align: left;
-}
-
-.page-header-btn {
-  text-align: right;
-}
 </style>
 <style scoped>
 .fade-enter {

+ 34 - 0
web_src/src/layout/userCenter.vue

@@ -0,0 +1,34 @@
+<template>
+  <div id="userCenter" class="u-view">
+    <el-header>
+      <user_header/>
+    </el-header>
+
+    <el-main class="u-main">
+      <el-container>
+        <transition name="fade">
+          <router-view></router-view>
+        </transition>
+      </el-container>
+    </el-main>
+
+  </div>
+
+</template>
+
+<script>
+import User_header from "@/components/layoutCom/user_header";
+export default {
+  name: "userCenter",
+  components: {User_header}
+}
+</script>
+
+<style scoped>
+.u-view{
+  /*background-color: #fff;*/
+  height: auto;
+}
+.u-main{
+}
+</style>

+ 2 - 15
web_src/src/main.js

@@ -6,7 +6,7 @@ import 'element-ui/lib/theme-chalk/index.css';
 
 import "./assets/index.css";
 import router from './router/index.js';
-import axios from 'axios';
+import axios from '@/apiStore/axios';
 import VueCookies from 'vue-cookies';
 import echarts from 'echarts';
 
@@ -38,24 +38,11 @@ Vue.use(VueClipboard);
 Vue.use(ElementUI);
 Vue.use(VueCookies);
 Vue.use(VueClipboards);
-Vue.prototype.$axios = axios;
+Vue.prototype.$axios = axios.axios;
 Vue.prototype.$notify = Notification;
 Vue.use(Contextmenu);
 
-axios.defaults.baseURL = (process.env.NODE_ENV === 'development') ? process.env.BASE_API : "";
 
-// api 返回401自动回登陆页面
-axios.interceptors.response.use(function (response) {
-  // 对响应数据做点什么
-  return response;
-}, function (error) {
-  // 对响应错误做点什么
-  if (error.response.status === 401) {
-    console.log("Received 401 Response")
-    router.push('/login');
-  }
-  return Promise.reject(error);
-});
 
 Vue.prototype.$cookies.config(60*30);
 

+ 26 - 5
web_src/src/map/ai.js

@@ -30,11 +30,26 @@ const triggerTypeEnum = {
   },
 }
 
+const alarmStateEnum = {
+  unread: {
+    val: 1,
+    text: '未阅读'
+  },
+  noAlarm: {
+    val: 2,
+    text: '不警告'
+  },
+  readed: {
+    val: 3,
+    text: '已读'
+  }
+}
+
 /**
  * 触发类型
  * @type {{unlimited: number, blackList: number, whiteList: number}}
  */
-const triggerTypes = {
+export const triggerTypes = {
   unlimited:triggerTypeEnum.unlimited.val,
   whiteList:triggerTypeEnum.whiteList.val,
   blackList:triggerTypeEnum.blackList.val,
@@ -66,7 +81,7 @@ function toTriggerType(num){
   return key?triggerTypeEnum[key]:false;
 }
 
-function getAITypeArr(){
+export function getAITypeArr(){
   return Object.keys(arithmeticEnum).map(key=>{
     return {
       ...arithmeticEnum[key],
@@ -75,7 +90,7 @@ function getAITypeArr(){
   })
 }
 
-function getTriggerTypeArr(){
+export function getTriggerTypeArr(){
   return Object.keys(triggerTypeEnum).map(key=>{
     return {
       ...triggerTypeEnum[key],
@@ -85,7 +100,11 @@ function getTriggerTypeArr(){
 }
 
 
-
+export function toAlarmState(num){
+  num=toNumber(num);
+  let key = Object.keys(alarmStateEnum).find(key=>alarmStateEnum[key].val===num);
+  return key?alarmStateEnum[key]:false;
+}
 
 export default {
   arithmeticEnum,
@@ -95,5 +114,7 @@ export default {
   toTriggerType,
   triggerTypes,
   getAITypeArr,
-  getTriggerTypeArr
+  getTriggerTypeArr,
+  alarmStateEnum,
+  toAlarmState
 }

+ 34 - 5
web_src/src/router/index.js

@@ -26,6 +26,11 @@ import rtcPlayer from '../components/dialog/rtcPlayer.vue'
 import aiLib from "@/components/AiLib";
 import createLib from "@/components/CreateLib";
 import editLib from "@/components/editLib";
+import bell from "@/components/bell";
+import alarmInfo from "@/components/mediaView";
+
+import userCenter from "@/layout/userCenter";
+import userInfo from "@/components/u_page/u_info";
 const originalPush = VueRouter.prototype.push
 VueRouter.prototype.push = function push(location) {
   return originalPush.call(this, location).catch(err => err)
@@ -78,6 +83,16 @@ export default new VueRouter({
           name: 'aiLib',
           component: aiLib,
         },
+        {
+          path: '/bell',
+          name: 'bell',
+          component: bell,
+        },
+        {
+          path: '/alarm/:alarmId',
+          name: 'alarmInfo',
+          component: alarmInfo,
+        },
         {
           path: '/createLib',
           name: 'createLib',
@@ -138,11 +153,7 @@ export default new VueRouter({
           name: 'map',
           component: map,
         },
-        {
-          path: '/userManager',
-          name: 'userManager',
-          component: userManager,
-        }
+
         ]
     },
     {
@@ -155,6 +166,24 @@ export default new VueRouter({
       name: 'deviceTree',
       component: deviceTree,
     },
+    {
+      path: '/user',
+      name: 'userCenter',
+      component: userCenter,
+      children: [
+        {
+          path: '/userInfo',
+          name: 'userInfo',
+          component: ()=>import("@/components/u_page/u_info"),
+        },
+        {
+          path: '/userManager',
+          name: 'userManager',
+          component: userManager,
+        },
+
+      ]
+    },
     {
       path: '/play/wasm/:url',
       name: 'wasmPlayer',

+ 6 - 0
web_src/src/until/EventTool.js

@@ -0,0 +1,6 @@
+class EventTool{
+
+  constructor() {
+  }
+
+}

+ 1 - 0
web_src/src/until/handle.js

@@ -2,6 +2,7 @@
 
 
 function handle (promise){
+  console.log(promise);
     return new Promise(resolve => {
         try{
             promise.then(val => {

+ 47 - 0
web_src/src/until/time.js

@@ -0,0 +1,47 @@
+/*
+ * @Description: 时间数据格式
+ * @Autor: kindring
+ * @Date: 2021-10-14 18:53:00
+ * @LastEditors: kindring
+ * @LastEditTime: 2021-11-11 15:28:03
+ * @LastDescript:
+ */
+/**
+ * 字符串格式化,自动填充0 不限大小写
+ * @param {*} date 时间对象
+ * @param {*} format 需要转换的格式 YYYY 年 MM月 DD 日 H小时 m分钟 s秒
+ * @returns
+ */
+function dateFormat(date, format = 'YYYY-MM-DD H:m:s') {
+    format = format.toLocaleUpperCase();
+    const config = {
+        YYYY: date.getFullYear(),
+        YY: date.getFullYear(),
+        MM: ('0' + (date.getMonth() + 1)).substr(-2, 2), //getMonth() 方法根据本地时间返回指定日期的月份(从 0 到 11)
+        DD: ('0' + date.getDate()).substr(-2, 2),
+        H: ('0' + date.getHours()).substr(-2, 2),
+        M: ('0' + date.getMinutes()).substr(-2, 2),
+        S: ('0' + date.getSeconds()).substr(-2, 2),
+    }
+    // console.log(config);
+    for (const key in config) {
+        format = format.replace(key, config[key])
+    }
+    return format
+}
+
+/**
+ * 时间戳字符转时间对象
+ * @param timeStamp 时间戳
+ * @param s 差距
+ * @returns {Date}
+ */
+function timeStamp_to_Date(timeStamp,s = 1000){
+    timeStamp = timeStamp * s;
+    return new Date(timeStamp);
+}
+
+export default {
+    dateFormat,
+    timeStamp_to_Date
+}

BIN
web_src/static/favicon.ico


+ 62 - 48
参考文档/数据库扩展.md

@@ -3,39 +3,39 @@
 > 以及使用的图像库
 > 
 ## 图像库 ai_library
-| 字段    | 类型     | 可选值 | 备注  |
-|-------|--------| --- |-----|
-| libraryId | int    | pk | 图像库id  |
-| libraryName | string | "" | 图像库名称  |
-| version | string | "" | 图像库 版本uuid |
-| arithmetic | int    | 1-3 | 适用的算法 |
-| total | int    | n | 图像数量 |
+| 字段          | 类型     | 可选值 | 备注         |
+|-------------|--------|-----|------------|
+| libraryId   | int    | pk  | 图像库id      |
+| libraryName | string | ""  | 图像库名称      |
+| version     | string | ""  | 图像库 版本uuid |
+| arithmetic  | int    | 1-3 | 适用的算法      |
+| total       | int    | n   | 图像数量       |
 
 ### 图像 lib_item
 | 字段        | 类型     | 可选值 | 备注            |
-|-----------|--------| --- |---------------|
-| itemId    | int    | pk | 项目id          |
-| libraryId | int    | tk | 图像库id         |
+|-----------|--------|-----|---------------|
+| itemId    | int    | pk  | 项目id          |
+| libraryId | int    | tk  | 图像库id         |
 | itemType  | int    | 1,2 | 数据类型,图片,文本    |
 | imageUrl  | string | url | 图片的链接         |
-| trait     | string | '' | 用来匹配的图片的特征值   |
-| itemName  | string | '' | 名称,用户名称或者车辆名称 |
-| itemNo    | string | '' | 标记号,用于车牌或者工号  |
-| idCard    | string | '' | 身份证号          |
-| carNo     | string | '' | 车牌号           |
+| trait     | string | ''  | 用来匹配的图片的特征值   |
+| itemName  | string | ''  | 名称,用户名称或者车辆名称 |
+| itemNo    | string | ''  | 标记号,用于车牌或者工号  |
+| idCard    | string | ''  | 身份证号          |
+| carNo     | string | ''  | 车牌号           |
 
 ## ai配置 aiConfigs
-| 字段    | 类型     | 可选值 | 备注  |
-|-------|--------| --- |-----|
-| configId | int    | pk | 配置id |
-| libraryId | int    | tk | 图形库id |
-| arithmetic | int    | 1-3 | 对应的算法.人脸,火情,车牌, |
-| triggerType | int    | 1-3 | 触发类型 无限制,白名单,黑名单 |
-| refreshTime | int    | 0-n | 刷新时间 单位秒 |
-| score | float  | 0.0-100.0 | 检测阈值 |
-| resourcePath | string | url | 资源数据更新获取地址 |
-| uploadUrl | string | url | 多媒体资源上传地址 |
-| pushUrl | string | url | 回调地址 |
+| 字段           | 类型     | 可选值       | 备注               |
+|--------------|--------|-----------|------------------|
+| configId     | int    | pk        | 配置id             |
+| libraryId    | int    | tk        | 图形库id            |
+| arithmetic   | int    | 1-3       | 对应的算法.人脸,火情,车牌,  |
+| triggerType  | int    | 1-3       | 触发类型 无限制,白名单,黑名单 |
+| refreshTime  | int    | 0-n       | 刷新时间 单位秒         |
+| score        | float  | 0.0-100.0 | 检测阈值             |
+| resourcePath | string | url       | 资源数据更新获取地址       |
+| uploadUrl    | string | url       | 多媒体资源上传地址        |
+| pushUrl      | string | url       | 回调地址             |
 
 
 资源获取地址 /api/device/lib?libId=xxxx
@@ -43,30 +43,44 @@
 多媒体资源上传地址 
 
 ### 国标设备配置 dev_ai_config
-| 字段    | 类型  | 可选值 | 备注  |
-|-------|-----| --- |-----|
-| deviceId | int | tk | 设备id |
-| configId | int | tk | 配置id |
+| 字段         | 类型  | 可选值 | 备注              |
+|------------|-----|-----|-----------------|
+| deviceId   | int | tk  | 设备id            |
+| configId   | int | tk  | 配置id            |
 | arithmetic | int | 1-3 | 对应的算法.人脸,火情,车牌, |
 
 ### ai告警库 ai_alarm
-| 字段         | 类型  | 可选值 | 备注                  |
-|------------|-----| --- |---------------------|
-| alarmId    | pk | |                     |
-| arithmetic | int | 1-3 | 算法                  |
-| mediaPath  | varchar | 255 | 资源地址                |
-| alarmState | int | 1-5 | 告警状态 1 未读, 2忽略,3已处理 |  
-| createTime | varchar | 255 | 报警时间, 时间戳           |
-| operationTime | varchar | 255 | 操作时间,时间戳            |
+| 字段               | 类型      | 可选值 | 备注                  |
+|------------------|---------|-----|---------------------|
+| alarmId          | pk      |     |                     |
+| arithmetic       | int     | 1-3 | 算法                  |
+| mediaPath        | varchar | 255 | 资源地址                |
+| rawMediaPath     | varchar | 255 | 资源地址                |
+| alarmState       | int     | 1-5 | 告警状态 1 未读, 2忽略,3已处理 |  
+| createTime       | varchar | 255 | 报警时间, 时间戳           |
+| operationTime    | varchar | 255 | 操作时间,时间戳            |
+| triggerType      | int     |     | 触发类型                |
+| deviceId         | varchar | '' | 设备id                |
+| channelId        | varchar | '' | 通道id                |
+| firmware_version | varchar | '' | 固件版本                |
+| devTime          | varchar | '' | 设备创建时间              |
+| signal           | varchar | '' | 信号                  |
+| temp_env         | varchar | '' | 环境温度                |
+| temp_cpu         | varchar | '' | cpu温度               |
+| ccid         | varchar | '' | ccid                |
+| zoom_rate         | varchar | '' | 对焦信息                |
+
 
 ### ai告警库 ai_alarm_item
-| 字段       | 类型 | 可选值 | 备注  |
-|----------|--| --- |---------------------|
-| itemId   | int | pk | id |
-| alarmId  | int | tk | id |
-|score | float | 0-100 | 相似度 |
-|x1 | float | n | 位置x1 |
-|x2 | float | n | 位置x2 |
-|y1 | float | n | 位置y1 |
-|y2 | float | n | 位置y2 |
-| info | Varchar | | 文字信息 |
+| 字段      | 类型      | 可选值   | 备注   |
+|---------|---------|-------|------|
+| itemId  | int     | pk    | id   |
+| alarmId | int     | tk    | id   |
+| score   | float   | 0-100 | 相似度  |
+| x1      | float   | n     | 位置x1 |
+| x2      | float   | n     | 位置x2 |
+| y1      | float   | n     | 位置y1 |
+| y2      | float   | n     | 位置y2 |
+| info    | Varchar |       | 文字信息 |
+| trait   | varchar | ''    | 特征码  |
+| uid     | varchar | ''    | uid  |

+ 30 - 6
参考文档/订阅通知实现ai识别.md

@@ -238,13 +238,7 @@ a(开始检查数据) --> b[请求url]
  e --> 更新数据 -->wait
 ```
 
-服务端返回数据结构 json
 
-| 字段    | 类型 | 备注                           |
-|-------| --- |------------------------------|
-| rcode | int | 调用状态码 0:成功,携带指定数据 2:无版本号     |
-| v     | string | 版本号 生成规则 hfy+随机uid,指定接口只有一个值 |
-| data | array | 识别名单列表                       |
 
 ## 设备端触发通知
 > 设备端在指定情况下推送消息到gb服务端
@@ -296,11 +290,41 @@ a(开始检查数据) --> b[请求url]
 </HfyAiInfo>
 </Notify>
 ```
+## 通知设备ai库更新
+<? xmlversion="1.0"?>
+<Response>
+<CmdType>update </CmdType>
+<SN>17430</SN>
+<DeviceID>64010000001340000001</DeviceID>
+<Result>OK</Result>
+</Response>
 
 ## ai识别到的多媒体数据上传
 > 摄像头会在识别时会有对应的图像或者录像数据,这部分得看怎么和通知绑定起来
 1. 通过 http post 的方式上传文件,但是参数携带 MediaId 
 
+## http接口定义
+### 设备获取数据库列表
+1. 接口地址 /aiLib/list/{libId}
+2. 请求参数 
+| 字段 | 位置 | 必须? | 备注 |
+| --- | --- | --- | --- |
+| libId | path | y | 数据库id 由wvp平台下发指定 |
+| v | query | n | 数据库版本号 |
+| p | version | n | 页码 默认1 |
+| l | limit | n | 每页数量 默认50 |
+
+3. 服务端返回数据结构 json
+| 字段    | 类型 | 备注                           |
+|-------| --- |------------------------------|
+| code | int | 调用状态码 0:成功,携带指定数据 2:无版本号     |
+| v     | string | 版本号 生成规则 hfy+随机uid,指定接口只有一个值 |
+| data | array | 识别名单列表,为0时存在                      |
+| page | int | 页码                      |
+| limit | int | 限制值                      |
+| total | int | 总数量                      |
+
+