Jelajahi Sumber

提交一部分

lgy 4 tahun lalu
induk
melakukan
fdc94be9e2
41 mengubah file dengan 3295 tambahan dan 213 penghapusan
  1. 93 27
      pom.xml
  2. 2 0
      src/main/java/com/persagy/ZktProjectAlarmApplication.java
  3. 356 0
      src/main/java/com/persagy/cache/AlarmInfoCache.java
  4. 68 0
      src/main/java/com/persagy/cache/CurrentDataCache.java
  5. 129 0
      src/main/java/com/persagy/client/GroupNettyClient.java
  6. 127 0
      src/main/java/com/persagy/client/GroupNettyClientHandler.java
  7. 213 0
      src/main/java/com/persagy/client/WebSocketClientFactory.java
  8. 104 0
      src/main/java/com/persagy/config/QuartzConfiguration.java
  9. 66 0
      src/main/java/com/persagy/constant/RedisConstant.java
  10. 15 0
      src/main/java/com/persagy/controller/HelloWorld.java
  11. 140 0
      src/main/java/com/persagy/demo/netty/websocket/client/WebSocketClient.java
  12. 147 0
      src/main/java/com/persagy/demo/netty/websocket/client/WebSocketClientHandler.java
  13. 17 0
      src/main/java/com/persagy/demo/netty/websocket/server/MyChannelHandlerPool.java
  14. 87 0
      src/main/java/com/persagy/demo/netty/websocket/server/MyWebSocketHandler.java
  15. 59 0
      src/main/java/com/persagy/demo/netty/websocket/server/NettyServer.java
  16. 89 0
      src/main/java/com/persagy/entity/AlarmDefine.java
  17. 154 0
      src/main/java/com/persagy/entity/AlarmRecord.java
  18. 138 0
      src/main/java/com/persagy/entity/AlarmState.java
  19. 58 0
      src/main/java/com/persagy/entity/Condition.java
  20. 14 3
      src/main/java/com/persagy/entity/NettyMessage.java
  21. 9 6
      src/main/java/com/persagy/init/InitRunner.java
  22. 53 0
      src/main/java/com/persagy/job/AlarmExpireJob.java
  23. 14 0
      src/main/java/com/persagy/service/AlarmDefineService.java
  24. 11 0
      src/main/java/com/persagy/service/AlarmHandleService.java
  25. 11 0
      src/main/java/com/persagy/service/AlarmQuartzService.java
  26. 86 0
      src/main/java/com/persagy/service/impl/AlarmDefineServiceImpl.java
  27. 233 0
      src/main/java/com/persagy/service/impl/AlarmHandleServiceImpl.java
  28. 62 0
      src/main/java/com/persagy/service/impl/AlarmQuartzServiceImpl.java
  29. 61 0
      src/main/java/com/persagy/service/impl/RunScriptExample.java
  30. 33 0
      src/main/java/com/persagy/utils/DateUtils.java
  31. 45 0
      src/main/java/com/persagy/utils/GZIPCompressUtil.java
  32. 0 168
      src/main/java/com/persagy/utils/IdWorker.java
  33. 87 0
      src/main/java/com/persagy/utils/LockUtil.java
  34. 158 4
      src/main/java/com/persagy/utils/StringUtil.java
  35. 44 1
      src/main/resources/application.yml
  36. 3 3
      src/main/resources/logback-spring.xml
  37. 44 0
      src/main/resources/quartz.properties
  38. 12 0
      src/main/resources/statements.av
  39. 179 0
      src/main/resources/zkt_alarm_tables_mysql_innodb.sql
  40. 64 0
      src/test/java/com/persagy/zktprojectalarm/Mytest.java
  41. 10 1
      src/test/java/com/persagy/zktprojectalarm/ZktProjectAlarmApplicationTests.java

+ 93 - 27
pom.xml

@@ -36,16 +36,13 @@
             <!--在引用时请在maven中央仓库搜索最新版本号 -->
             <version>2.0.2</version>
         </dependency>
+        <!--guava-->
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <version>20.0</version>
         </dependency>
-        <dependency>
-            <groupId>com.persagy.zkt</groupId>
-            <artifactId>zkt-brain</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
+        <!--lombok-->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
@@ -53,10 +50,17 @@
         </dependency>
         <!-- 数学逻辑运算解析库-->
         <dependency>
-            <groupId>org.mariuszgromada.math</groupId>
-            <artifactId>MathParser.org-mXparser</artifactId>
-            <version>4.4.0</version>
+            <groupId>com.googlecode.aviator</groupId>
+            <artifactId>aviator</artifactId>
+            <version>5.1.4</version>
+        </dependency>
+        <!-- netty -->
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.42.Final</version>
         </dependency>
+        <!--测试工具-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
@@ -68,25 +72,87 @@
                 </exclusion>
             </exclusions>
         </dependency>
-    </dependencies>
+        <!--WebSocket核心依赖包-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        <!--WebSocket核心依赖包-->
+        <dependency>
+            <groupId>org.java-websocket</groupId>
+            <artifactId>Java-WebSocket</artifactId>
+            <version>1.3.8</version>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>junit</groupId>-->
+<!--            <artifactId>junit</artifactId>-->
+<!--        </dependency>-->
+       <!-- commons-lang3常用工具类-->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <!--fastjson JSON组件-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.71</version>
+            <scope>compile</scope>
+        </dependency>
+        <!--hutool工具类-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.4.3</version>
+            <scope>compile</scope>
+        </dependency>
+        <!--定时任务框架-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+        </dependency>
+        <!--  MYSQL-->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+       <!--  Spring Boot JDBC-->
+          <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <!-- 数据库连接池-->
+       <dependency>
+           <groupId>com.alibaba</groupId>
+           <artifactId>druid-spring-boot-starter</artifactId>
+           <version>1.1.22</version>
+       </dependency>
+        <!-- spring-data-jpa -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+   </dependencies>
 
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.8.1</version>
-                <configuration>
-                    <source>1.8</source>
-                    <target>1.8</target>
-                    <encoding>UTF-8</encoding>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.springframework.boot</groupId>
-                <artifactId>spring-boot-maven-plugin</artifactId>
-            </plugin>
-        </plugins>
-    </build>
+   <build>
+       <plugins>
+           <plugin>
+               <groupId>org.apache.maven.plugins</groupId>
+               <artifactId>maven-compiler-plugin</artifactId>
+               <version>3.8.1</version>
+               <configuration>
+                   <source>1.8</source>
+                   <target>1.8</target>
+                   <encoding>UTF-8</encoding>
+                   <skip>true</skip>
+               </configuration>
+           </plugin>
+           <plugin>
+               <groupId>org.springframework.boot</groupId>
+               <artifactId>spring-boot-maven-plugin</artifactId>
+           </plugin>
+       </plugins>
+   </build>
 
 </project>

+ 2 - 0
src/main/java/com/persagy/ZktProjectAlarmApplication.java

@@ -2,8 +2,10 @@ package com.persagy;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 @SpringBootApplication
+@EnableScheduling
 public class ZktProjectAlarmApplication {
 
     public static void main(String[] args) {

+ 356 - 0
src/main/java/com/persagy/cache/AlarmInfoCache.java

@@ -0,0 +1,356 @@
+package com.persagy.cache;
+
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.googlecode.aviator.AviatorEvaluator;
+import com.googlecode.aviator.Expression;
+import com.persagy.entity.AlarmDefine;
+import com.persagy.entity.AlarmState;
+import com.persagy.entity.Condition;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * @description: 报警存放报警定义缓存的的数据
+ * @author: LuoGuangyi
+ * @company: Persagy Technology Co.,Ltd
+ * @since: 2020/10/19 10:57
+ * @version: V1.0
+ **/
+@Component
+@Slf4j
+public class AlarmInfoCache {
+
+    /**
+     * 报警定义  <报警定义标识,报警定义详情>
+     * 报警定义标识默认为:报警编码-报警对象id
+     */
+    private ConcurrentHashMap<String, AlarmDefine> alarmDefineMap = new ConcurrentHashMap<>();
+    /**
+     * 信息点-报警定义 <表号-功能号,[报警定义1,报警定义2]>
+     */
+    private ConcurrentHashMap<String, List<AlarmDefine>> infoAlarmMap = new ConcurrentHashMap<>();
+
+
+
+    /**
+     * 报警定义  <报警定义标识,报警时间状态>
+     * 报警定义标识默认为:报警编码-报警对象id
+     */
+    private ConcurrentHashMap<String, AlarmState> alarmStateMap = new ConcurrentHashMap<>();
+    /**
+     * @description:获取报警状态  <报警定义标识,报警时间状态>
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param :
+     * @return: null
+     * @since: 2020/10/20 17:12
+     * @version: V1.0
+     */
+    public AlarmState getAlarmState(String definitionId) {
+        return alarmStateMap.get(definitionId);
+    }
+    /**
+     * @description:设置报警定义
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param definitionId: 报警定义id
+     * @param alarmState: 报警状态时间类
+     * @return: com.persagy.entity.AlarmState
+     * @since: 2020/10/20 17:15
+     * @version: V1.0
+     */
+    public AlarmState setAlarmState(String definitionId,AlarmState alarmState) {
+        return alarmStateMap.put(definitionId,alarmState);
+    }
+    /**
+     * 获取报警定义Map <报警定义标识,报警定义详情>
+     * @return
+     */
+    public ConcurrentHashMap<String, AlarmDefine> getAlarmDefineMap() {
+        return alarmDefineMap;
+    }
+    /**
+     * 设置报警定义Map <报警定义标识,报警定义详情>
+     * @return
+     */
+    public void setAlarmDefineMap(ConcurrentHashMap<String, AlarmDefine> alarmDefineMap) {
+        this.alarmDefineMap = alarmDefineMap;
+    }
+    /**
+     * 获取信息点-报警定义map <表号-功能号,[报警定义1,报警定义2]>
+     * @return
+     */
+    public ConcurrentHashMap<String, List<AlarmDefine>> getInfoAlarmMap() {
+        return infoAlarmMap;
+    }
+    /**
+     * 设置信息点-报警定义map <表号-功能号,[报警定义1,报警定义2]>
+     * @return
+     */
+    public void setInfoAlarmMap(ConcurrentHashMap<String, List<AlarmDefine>> infoAlarmMap) {
+        this.infoAlarmMap = infoAlarmMap;
+    }
+
+    /**
+     * @description:根据报警定义id获取报警定义详情
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param definitionId :
+     * @return: AlarmDefine 报警定义详情
+     * @since: 2020/10/19 14:32
+     * @version: V1.0
+     */
+    public AlarmDefine getAlarmDefinitionById(String definitionId) {
+        return alarmDefineMap.get(definitionId);
+    }
+
+
+    /**
+     * @description:保存报警定义详情
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param definitionId:报警定义ID
+     * @param alarmDefine:报警定义详情
+     * @return: void
+     * @since: 2020/10/19 14:32
+     * @version: V1.0
+     */
+    public void putAlarmDefinitionById(String definitionId, AlarmDefine alarmDefine) {
+        Condition condition = alarmDefine.getCondition();
+        List<JSONObject> infoCodes = condition.getInfoCodes();
+        String trigger = condition.getTrigger();
+        String end = condition.getEnd();
+        try {
+            Expression triggerCompiledExp = AviatorEvaluator.compile(trigger,true);
+        } catch (Exception e) {
+            log.info("触发表达式:",trigger);
+            log.error("触发表表达式不合法,请校验",e);
+            throw  new IllegalArgumentException("触发表表达式不合法",e);
+        }
+        try {
+            Expression endCompiledExp = AviatorEvaluator.compile(end,true);
+        } catch (Exception e) {
+            log.error("恢复表达式:",end);
+            log.error("恢复表达式不合法,请校验",e);
+            throw  new IllegalArgumentException("恢复表达式不合法",e);
+        }
+        alarmDefineMap.put(definitionId, alarmDefine);
+        for (int i = 0; i < infoCodes.size(); i++) {
+            JSONObject infoCode = infoCodes.get(i);
+            String meterId = infoCode.getString("meterId");
+            String funcId = infoCode.getString("funcId");
+            putAlarmDefinitionIdByMeterFuncId(meterId,funcId,alarmDefine);
+        }
+    }
+    public void putAlarmDefinitionById(AlarmDefine alarmDefine) {
+        putAlarmDefinitionById(getAlarmDefineId(alarmDefine),alarmDefine);
+    }
+
+
+
+    /**
+     * @description:根据表号功能号获取多条报警定义id
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param meterId: 表号
+     * @param funcId: 功能号
+     * @return: java.util.List<com.alibaba.fastjson.JSONObject>
+     * @since: 2020/10/19 14:31
+     * @version: V1.0
+     */
+    public List<AlarmDefine> getAlarmDefinitionIdByMeterFuncId(String meterId, String funcId) {
+        return infoAlarmMap.get(getKey(meterId,funcId));
+    }
+
+
+    /**
+     * @description:根据表号功能号缓存报警定义
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param meterId: 表号
+     * @param funcId: 功能号
+     * @param definition: 报警定义
+     * @return: void
+     * @since: 2020/10/19 14:26
+     * @version: V1.0
+     */
+    public void putAlarmDefinitionIdByMeterFuncId(String meterId, String funcId, AlarmDefine definition) {
+        List<AlarmDefine> definitionList = getAlarmDefinitionIdByMeterFuncId(meterId, funcId);
+        if (CollectionUtils.isEmpty(definitionList)) {
+            definitionList = new ArrayList<>();
+        }
+        //去重并添加
+        definitionList = definitionList.stream().filter(a->!getAlarmDefineId(definition).equals(getAlarmDefineId(a))).collect(Collectors.toList());
+        definitionList.add(definition);
+        infoAlarmMap.put(getKey(meterId,funcId), definitionList);
+    }
+
+
+    /**
+     * @description:删除所有缓存的报警定义
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @return: void
+     * @since: 2020/10/19 14:25
+     * @version: V1.0
+     */
+    public void clearAllAlarmDefine() {
+        alarmDefineMap.clear();
+        infoAlarmMap.clear();
+        //清除编译表达式缓存
+        AviatorEvaluator.getInstance().clearExpressionCache();
+    }
+    /**
+     * @description:获取报警定义id,
+     * 中台报警定义ID没有实际意义,根据itemCode-objId拼接作为唯一标识
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param alarmDefine:
+     * @since: 2020/10/19 17:28
+     * @version: V1.0
+     */
+    public String getAlarmDefineId(AlarmDefine alarmDefine) {
+        return alarmDefine.getItemCode()+"-"+alarmDefine.getObjId();
+    }
+
+    /**
+     * @description:判断是否包含有改表号功能号的改报警定义
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param meterId:表号
+     * @param funcId:功能号
+     * @return: boolean
+     * @since: 2020/10/19 19:54
+     * @version: V1.0
+     */
+    public boolean hasKey(String meterId,String funcId) {
+        return infoAlarmMap.containsKey(getKey(meterId,funcId));
+    }
+
+    /**
+     * 拼接表号-功能号为一个key
+     * @param meterId
+     * @param funcId
+     * @return
+     */
+    private String getKey(String meterId,String funcId) {
+        return meterId + "-" + funcId;
+    }
+//
+//    /**
+//     * @description: 根据项目id, 对象id, 信息点编码获取表号功能号
+//     * @param: projectId 项目id
+//     * @param: objectId 对象id
+//     * @param: infoCode 信息点编码
+//     * @return: java.lang.String
+//     * @exception:
+//     * @author: fenghanchao
+//     * @company: Persagy Technology Co.,Ltd
+//     * @since: 10:34 2020/7/22
+//     * @version: V1.0
+//     */
+//    public String getMeterFuncIdByObjectInfoCode(String projectId, String objectId, String infoCode) {
+//        return (String) redisTemplate.opsForHash().get(RedisConstant.ALARM_DEFINITION_METER_FUNC_KEY + ":" + projectId, objectId + "-" + infoCode);
+//    }
+//
+//    /**
+//     * @description: 设置项目id, 对象id, 信息点编码获取表号功能号对应关系
+//     * @param: projectId 项目id
+//     * @param: objectId 对象id
+//     * @param: infoCode 信息点编码
+//     * @param: meterFuncId 表号功能号
+//     * @return: void
+//     * @exception:
+//     * @author: fenghanchao
+//     * @company: Persagy Technology Co.,Ltd
+//     * @since: 10:36 2020/7/22
+//     * @version: V1.0
+//     */
+//    public void putMeterFuncIdByObjectInfoCode(String projectId, String objectId, String infoCode, String meterFuncId) {
+//        redisTemplate.opsForHash().put(RedisConstant.ALARM_DEFINITION_METER_FUNC_KEY + ":" + projectId, objectId + "-" + infoCode, meterFuncId);
+//    }
+//
+//    /**
+//     * @description: 根据报警定义id获取预报警时间信息
+//     * @param: projectId 项目id
+//     * @param: definitionId 报警定义id
+//     * @return: java.lang.String (例 2020-07-21 00:00:00)
+//     * @exception:
+//     * @author: fenghanchao
+//     * @company: Persagy Technology Co.,Ltd
+//     * @since: 15:37 2020/7/20
+//     * @version: V1.0
+//     */
+//    public String getPreAlarmTimeByDefinitionId(String projectId, String definitionId) {
+//        return (String) redisTemplate.opsForHash().get(RedisConstant.ALARM_DEFINITION_PRE_ALARM_KEY + ":" + projectId, definitionId);
+//    }
+//
+//    public void deletePreAlarmTimeByDefinitionId(String projectId, String definitionId) {
+//        redisTemplate.opsForHash().delete(RedisConstant.ALARM_DEFINITION_PRE_ALARM_KEY + ":" + projectId, definitionId);
+//    }
+//
+//    /**
+//     * @description: 设置报警定义id与预报警时间信息的对应关系
+//     * @param: projectId
+//     * @param: definitionId
+//     * @param: preAlarmDate
+//     * @return: void
+//     * @exception:
+//     * @author: fenghanchao
+//     * @company: Persagy Technology Co.,Ltd
+//     * @since: 15:39 2020/7/20
+//     * @version: V1.0
+//     */
+//    public void putPreAlarmTimeByDefinitionId(String projectId, String definitionId, Date preAlarmDate) throws Exception{
+//        redisTemplate.opsForHash().put(RedisConstant.ALARM_DEFINITION_PRE_ALARM_KEY + ":" + projectId, definitionId, DateUtils.date2Str(preAlarmDate, DateUtils.SDF_SECOND));
+//    }
+//
+//    /**
+//     * @description: 根据报警定义id获取报警详情
+//     * @param: projectId 项目id
+//     * @param: definitionId 报警定义id
+//     * @return: java.lang.String 报警详情json字符串 {...}
+//     * @exception:
+//     * @author: fenghanchao
+//     * @company: Persagy Technology Co.,Ltd
+//     * @since: 15:42 2020/7/20
+//     * @version: V1.0
+//     */
+//    public String getAlarmInfoByDefinitionId(String projectId, String definitionId) {
+//        return (String) redisTemplate.opsForHash().get(RedisConstant.ALARM_DEFINITION_ALARM_INFO_KEY + ":" + projectId, definitionId);
+//    }
+//
+//    /**
+//     * @description: 设备报警定义id报警详情对应关系
+//     * @param: projectId 项目id
+//     * @param: definitionId 报警定义id
+//     * @param: alarmInfo 报警详情
+//     * @return: void
+//     * @exception:
+//     * @author: fenghanchao
+//     * @company: Persagy Technology Co.,Ltd
+//     * @since: 15:42 2020/7/20
+//     * @version: V1.0
+//     */
+//    public void putAlarmInfoByDefinitionId(String projectId, String definitionId, String alarmInfo) {
+//        redisTemplate.opsForHash().put(RedisConstant.ALARM_DEFINITION_ALARM_INFO_KEY + ":" + projectId, definitionId, alarmInfo);
+//    }
+
+}

+ 68 - 0
src/main/java/com/persagy/cache/CurrentDataCache.java

@@ -0,0 +1,68 @@
+package com.persagy.cache;
+
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @description: 实时数据
+ * @author: feng
+ * @company: Persagy Technology Co.,Ltd
+ * @since: 2020/7/16 15:50
+ * @version: V1.0
+ */
+@Component
+public class CurrentDataCache {
+
+    private ConcurrentHashMap<String, Double> latestIOTDataMap = new ConcurrentHashMap<>();
+
+    /**
+     * @description:获取设备实时数据
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param meterId: 表号
+     * @param funcId: 功能号
+     * @return: java.lang.String
+     * @since: 2020/10/19 16:15
+     * @version: V1.0
+     */
+    public double getCurrentData(String meterId, String funcId) {
+        return latestIOTDataMap.get(getKey(meterId, funcId));
+    }
+
+    private String getKey(String meterId,String funcId) {
+        return meterId + "-" + funcId;
+    }
+
+    /**
+     * @description:存放实时数据
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param meterId: 表号
+     * @param funcId: 功能号
+     * @param currentData: 当前值
+     * @return: void
+     * @since: 2020/10/19 16:16
+     * @version: V1.0
+     */
+    public void putCurrentData(String meterId, String funcId, double currentData) {
+        latestIOTDataMap.put(getKey(meterId,funcId), currentData);
+    }
+
+    /**
+     * @description:判断是否包含有改表号功能号的数据
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param meterId:表号
+     * @param funcId:功能号
+     * @return: boolean
+     * @since: 2020/10/19 19:54
+     * @version: V1.0
+     */
+    public boolean hasKey(String meterId,String funcId) {
+        return latestIOTDataMap.containsKey(getKey(meterId,funcId));
+    }
+}

+ 129 - 0
src/main/java/com/persagy/client/GroupNettyClient.java

@@ -0,0 +1,129 @@
+package com.persagy.client;
+
+import com.persagy.service.AlarmDefineService;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.codec.LengthFieldPrepender;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author lqshi
+ * @ClassName: NettyClient
+ * @Description: netty启动、发送消息等
+ * @date 2020年8月31日 上午10:34:45
+ */
+@Service
+public class GroupNettyClient {
+    @Autowired
+    AlarmDefineService alarmDefineService;
+
+    static Bootstrap groupBootstrap = new Bootstrap();
+    static Channel channelGroup;
+    @Value("${group.alarm.host:127.0.0.1}")
+    public String host;
+    @Value("${group.alarm.port}")
+    public int port;
+    //会话对象组
+    public static Map<String, Channel> channelsMap = new ConcurrentHashMap<>();
+
+
+    /**
+     * @description:启动netty客户端
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param
+     * @return: void
+     * @since: 2020/10/20 15:54
+     * @version: V1.0
+     */
+    public void start() {
+
+        GroupNettyClient groupNettyClient = this;
+        try {
+            // 启动辅助对象类
+            groupBootstrap.group(new NioEventLoopGroup())
+                    // 建立通道
+                    .channel(NioSocketChannel.class)
+                    // 初始化通道及进行配置
+                    .handler(new ChannelInitializer<SocketChannel>() {
+                        @Override
+                        protected void initChannel(SocketChannel ch) throws Exception {
+                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
+                            ch.pipeline().addLast(new LengthFieldPrepender(4));
+                            // 将分隔之后的字节数据转换为字符串
+                            ch.pipeline().addLast(new StringDecoder());
+                            ch.pipeline().addLast(new StringEncoder());
+                            // pipeline可以理解为所有handler的初始化容器
+                            ch.pipeline().addLast(new GroupNettyClientHandler(groupNettyClient, alarmDefineService));// 添加自定义handler
+                        }
+                    });
+            // 连接远程节点,等待连接完成
+            // channel = b.connect().sync();//
+            // sync()代表同步等待连接,然后在f.channel().closeFuture().sync();,如果不加就不会等待,直接关闭
+            channelGroup = groupBootstrap.connect(host, port).channel();
+            channelsMap.put("group", channelGroup);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            // 关闭主线程组
+            //回收group后下面的自动重连connect()无法直接使用
+            //group.shutdownGracefully();
+
+        }
+    }
+
+    public void connect(GroupNettyClient groupNettyClient, Channel channel) {
+        // 加入断线后自动重连监听器
+        System.out.println("try connect!");
+        for (Map.Entry<String, Channel> entry : channelsMap.entrySet()) {
+            if (entry.getValue() == channel && !channel.isActive()) {
+                channelGroup = groupBootstrap.connect(groupNettyClient.host, groupNettyClient.port).addListener(new ChannelFutureListener() {
+                    @Override
+                    public void operationComplete(ChannelFuture future) throws Exception {
+                        if (future.cause() != null) {
+                            System.out.println("Failed to connect: " + future.cause());
+                        }
+                    }
+                }).channel();
+                channelsMap.put(entry.getKey(), channelGroup);
+            }
+        }
+    }
+
+    /**
+     * @param msg
+     * @Title: sendMessage
+     * @Description: 发送消息
+     */
+/*    public void sendMessage(Object msg,String type) {
+        for (Map.Entry<String, Channel> ch:channelsMap.entrySet() ) {
+            if(type.equals(ch.getKey())) {
+                System.out.println(msg);
+                System.out.println(ch.getKey());
+                ch.getValue().writeAndFlush(msg);
+            }else {
+                System.out.println("----to group --");
+                ch.getValue().writeAndFlush(msg);
+            }
+        }
+    }*/
+    public void sendMessage(Object msg) {
+        channelGroup.writeAndFlush(msg);
+    }
+
+}

+ 127 - 0
src/main/java/com/persagy/client/GroupNettyClientHandler.java

@@ -0,0 +1,127 @@
+package com.persagy.client;
+
+import com.persagy.entity.NettyMessage;
+import com.persagy.service.AlarmDefineService;
+import com.persagy.utils.StringUtil;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @ClassName: EchoClientHandler
+ * @Description: 客户端处理类
+ * @author lqshi
+ * @date 2020年8月29日 下午8:31:59
+ */
+// 注意:SimpleChannelInboundHandler<ByteBuf>的<>中是什么,channelRead0第二参数是什么
+@Slf4j
+public class GroupNettyClientHandler extends ChannelInboundHandlerAdapter {
+    // Sleep 5 seconds before a reconnection attempt.
+    static final int RECONNECT_DELAY = Integer.parseInt(System.getProperty("reconnectDelay", "5"));
+    // Reconnect when the server sends nothing for 10 seconds.
+    private static final int READ_TIMEOUT = Integer.parseInt(System.getProperty("readTimeout", "10"));
+
+   private AlarmDefineService alarmDefineService;
+   private GroupNettyClient groupNettyClient;
+
+    public GroupNettyClientHandler(GroupNettyClient groupNettyClient, AlarmDefineService alarmDefineService) {
+        this.alarmDefineService = alarmDefineService;
+        this.groupNettyClient = groupNettyClient;
+    }
+
+    public GroupNettyClientHandler() {
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
+
+        if (!(evt instanceof IdleStateEvent)) {
+            return;
+        }
+        IdleStateEvent e = (IdleStateEvent) evt;
+        if (e.state() == IdleState.READER_IDLE) {
+            System.out.println("no inbound traffic");
+            // The connection was OK but there was no traffic for last period.
+            // 长时间不操作的时候自动关闭连接; ctx.close();
+        }
+    }
+	 /**
+     * 在到服务器的连接已经建立之后将被调用
+     * @param ctx
+     * @throws Exception
+     */
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        System.out.println("Connected to: " + ctx.channel().remoteAddress());
+        //启动的时候发送消息
+       NettyMessage msg = new NettyMessage();
+        msg.setOpCode(3);
+        ctx.channel().writeAndFlush(msg.toString());
+    }
+    /**
+     * 当从服务器接收到一个消息时被调用
+     * @param ctx
+     * @param msg
+     * @throws Exception
+     */
+    @Override
+    public  void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        System.out.println("Client received: "+ msg);
+        try {
+            handlerMsg(ctx, msg);
+        } catch (Exception e) {
+            log.error("channelRead",e);
+        }
+    }
+
+    private void handlerMsg(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {
+        if(StringUtil.isJSONObject((String) msg)) {
+            NettyMessage message = StringUtil.tranferItemToDTO((String) msg, NettyMessage.class);
+            NettyMessage response = new NettyMessage();
+            System.out.println(message.toString());
+            channelHandlerContext.write(response.toString());
+        }
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        ctx.flush();
+    }
+
+    /**
+     * 在处理过程中引发异常时被调用
+     * @param ctx
+     * @param cause
+     * @throws Exception
+     */
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        cause.printStackTrace();
+        ctx.close();
+    }
+    
+    /**
+     * 通道处于非活跃状态动作,该方法只会在失效时调用一次
+     */
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+    	//客户端自己不正常情况下自己在重连一次
+        System.out.println("Disconnected from: " + ctx.channel().remoteAddress());
+
+    }
+    @Override
+    public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception {
+        System.out.println("Sleeping for: " + RECONNECT_DELAY + "s,Reconnecting to: " + groupNettyClient.host + ':' + groupNettyClient.port);
+        ctx.channel().eventLoop().schedule(new Runnable() {
+            @Override
+            public void run() {
+                System.out.println("Reconnecting to: " + groupNettyClient.host + ':' + groupNettyClient.port);
+                groupNettyClient.connect(groupNettyClient,ctx.channel());
+            }
+        }, RECONNECT_DELAY, TimeUnit.SECONDS);
+    }
+}

+ 213 - 0
src/main/java/com/persagy/client/WebSocketClientFactory.java

@@ -0,0 +1,213 @@
+package com.persagy.client;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.TimeInterval;
+import com.persagy.service.AlarmHandleService;
+import com.persagy.utils.GZIPCompressUtil;
+import com.persagy.utils.StringUtil;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.WebSocket;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @description: WebSocketClient连接,接受IOT采集数据
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/20 00209:23
+ * @version:V1.0
+ * 参考链接:https://blog.csdn.net/qq_35206261/article/details/103761773
+ * 使用说明:
+ * #1.发送消息
+ * String msg = "";
+ * webSocketClientFactory.sendMsg(webSocketClientFactory.getOutCallWebSocketClientHolder(), heartBeatMsg);
+ * #2.系统启动初始化连接
+ * // 项目启动的时候打开websocket连接
+ * webSocketClientFactory.retryOutCallWebSocketClient();
+ **/
+@Component
+@Slf4j
+@Data
+public class WebSocketClientFactory {
+    /**
+     * IP
+     */
+    @Value("${terminal.alarm.host:127.0.0.1}")
+    public String host;
+    /**
+     * 端口
+     */
+    @Value("${terminal.alarm.port}")
+    public int port;
+    /**
+     * 后缀
+     */
+    @Value("${terminal.alarm.suffix}")
+    String suffix;
+    /**
+     * 是否启用压缩
+     */
+    @Value("${terminal.alarm.compress}")
+    boolean compress;
+    /**
+     * 项目名称
+     */
+    @Value("${project.id}")
+    String projectId;
+
+    private WebSocketClient outCallWebSocketClientHolder;
+    @Autowired
+    AlarmHandleService alarmHandleService;
+
+
+    AtomicInteger total = new AtomicInteger(0);
+
+    /**
+     * 创建websocket对象
+     *
+     * @return WebSocketClient
+     * @throws URISyntaxException
+     */
+    private WebSocketClient createNewWebSocketClient() throws URISyntaxException {
+        String outCallWebSockertUrl = "ws://"+host+":"+port;
+        outCallWebSockertUrl+=StringUtil.isEmpty(suffix)?"":"/"+suffix;
+        log.info("连接地址为:[{}]",outCallWebSockertUrl);
+        WebSocketClient webSocketClient = new WebSocketClient(new URI(outCallWebSockertUrl)) {
+            @Override
+            public void onOpen(ServerHandshake serverHandshake) {
+                String msg = projectId;
+                sendMsg(getOutCallWebSocketClientHolder(), msg);
+            }
+
+            @Override
+            public void onMessage(String msg) {
+                String message = msg;
+                if (compress) {
+                    try {
+                        byte[] byteArrayMsg = GZIPCompressUtil.uncompress(msg.getBytes("ISO-8859-1"));
+                        message = new String(byteArrayMsg);
+                    } catch (Exception e) {
+                        log.error("解密失败!", e);
+                    }
+                }
+                log.info("接收信息为:{}", message);
+
+                try {
+                    alarmHandleService.handleIOTData(message);
+                } catch (Exception e) {
+                    log.error("数据处理失败", e);
+                }
+
+                total.getAndIncrement();
+            }
+
+            @Override
+            public void onClose(int i, String s, boolean b) {
+                log.info("关闭连接");
+                //retryOutCallWebSocketClient();
+            }
+
+            @Override
+            public void onError(Exception e) {
+                log.error("连接异常");
+                //retryOutCallWebSocketClient();
+            }
+        };
+        webSocketClient.connect();
+        return webSocketClient;
+    }
+
+
+    /**
+     * 项目启动或连接失败的时候打开新链接,进行连接认证
+     * 需要加同步,不然会创建多个连接
+     */
+    public synchronized WebSocketClient retryOutCallWebSocketClient() {
+        try {
+            // 关闭旧的websocket连接, 避免占用资源
+            WebSocketClient oldOutCallWebSocketClientHolder = this.getOutCallWebSocketClientHolder();
+            if (null != oldOutCallWebSocketClientHolder) {
+                log.info("关闭旧的websocket连接");
+                oldOutCallWebSocketClientHolder.close();
+            }
+
+            log.info("打开新的websocket连接,并进行认证");
+            WebSocketClient webSocketClient = this.createNewWebSocketClient();
+
+            // 每次创建新的就放进去
+            this.setOutCallWebSocketClientHolder(webSocketClient);
+            return webSocketClient;
+        } catch (URISyntaxException e) {
+            e.printStackTrace();
+            log.error(e.getMessage());
+        }
+        return null;
+    }
+
+
+    /**
+     * 发送消息
+     * 注意: 要加超时设置,避免很多个都在同时超时占用资源
+     *
+     * @param webSocketClient 指定的webSocketClient
+     * @param message         消息
+     */
+    public void sendMsg(WebSocketClient webSocketClient, String message) {
+        log.info("websocket向服务端发送消息,消息为:{}", message);
+        //long startOpenTimeMillis = System.currentTimeMillis();
+        TimeInterval timer = DateUtil.timer();
+        while (!webSocketClient.getReadyState().equals(WebSocket.READYSTATE.OPEN)) {
+            log.debug("正在建立通道,请稍等");
+            if (timer.interval() >= 5000) {
+                log.error("超过5秒钟还未打开连接,超时,不再等待");
+                return;
+            }
+        }
+        webSocketClient.send(message);
+    }
+
+    /**
+     * @param :
+     * @description:启动专门的定时任务去自动断开重新连接(10秒钟监测一次)
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @return: null
+     * @since: 2020/10/20 9:54
+     * @version: V1.0
+     */
+    @Scheduled(initialDelay = 20000, fixedDelay = 10000)
+    public void holderConneted() {
+        log.debug("连接状态监测中....");
+        try {
+            WebSocketClient outCallWebSocketClientHolder = this.getOutCallWebSocketClientHolder();
+            if (null == outCallWebSocketClientHolder) {
+                log.info("当前连接还未建立");
+                return;
+            }
+            if (!outCallWebSocketClientHolder.isOpen()) {
+                log.error("连接已经断开,正在重新连接,当前状态为[{}]", outCallWebSocketClientHolder.getReadyState());
+                if (outCallWebSocketClientHolder.getReadyState().equals(WebSocket.READYSTATE.NOT_YET_CONNECTED)) {
+                    outCallWebSocketClientHolder.connect();
+                } else if (outCallWebSocketClientHolder.getReadyState().equals(WebSocket.READYSTATE.CLOSING) || outCallWebSocketClientHolder.getReadyState().equals(WebSocket.READYSTATE.CLOSED)) {
+                    outCallWebSocketClientHolder.reconnect();
+                }
+            }
+        } catch (Exception e) {
+            log.error("连接发生异常,尝试重新连接", e);
+            //重连异常后删除旧的连接并重新连接
+            retryOutCallWebSocketClient();
+        }
+    }
+
+}

+ 104 - 0
src/main/java/com/persagy/config/QuartzConfiguration.java

@@ -0,0 +1,104 @@
+package com.persagy.config;
+
+import org.quartz.spi.JobFactory;
+import org.quartz.spi.TriggerFiredBundle;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.config.PropertiesFactoryBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.scheduling.quartz.SpringBeanJobFactory;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * @author LuoGuangyi
+ * @title: QuartzConfiguration
+ * @description: 定时任务配置文件
+ * @date 2019/09/10 14:21
+ */
+@Configuration
+@EnableScheduling
+public class QuartzConfiguration {
+	/**
+	 * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory 实现任务实例化方式
+	 */
+	public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
+
+		private transient AutowireCapableBeanFactory beanFactory;
+
+		@Override
+		public void setApplicationContext(final ApplicationContext context) {
+			beanFactory = context.getAutowireCapableBeanFactory();
+		}
+
+		/**
+		 * 将job实例交给spring ioc托管 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例
+		 * 
+		 * @param bundle
+		 * @return
+		 * @throws Exception
+		 */
+		@Override
+		protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
+			final Object job = super.createJobInstance(bundle);
+			/**
+			 * 将job实例交付给spring ioc
+			 */
+			beanFactory.autowireBean(job);
+			return job;
+		}
+	}
+
+	/**
+	 * 配置任务工厂实例
+	 * 
+	 * @param applicationContext
+	 *            spring上下文实例
+	 * @return
+	 */
+	@Bean
+	public JobFactory jobFactory(ApplicationContext applicationContext) {
+		/**
+		 * 采用自定义任务工厂 整合spring实例来完成构建任务 see {@link AutowiringSpringBeanJobFactory}
+		 */
+		AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
+		jobFactory.setApplicationContext(applicationContext);
+		return jobFactory;
+	}
+
+	// @Qualifier("dataSource0")
+	@Bean(name = "quartzScheduler")
+	public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory,
+			Properties quartzProperties) throws IOException {
+		SchedulerFactoryBean factory = new SchedulerFactoryBean();
+		// 将spring管理job自定义工厂交由调度器维护
+		factory.setJobFactory(jobFactory);
+		factory.setSchedulerName("quartzScheduler");
+		factory.setDataSource(dataSource);
+		// 设置覆盖已存在的任务
+		factory.setOverwriteExistingJobs(true);
+		// 项目启动完成后,等待2秒后开始执行调度器初始化
+		factory.setStartupDelay(20);
+		// 设置调度器自动运行
+		factory.setAutoStartup(true);
+		// factory.setConfigLocation(new ClassPathResource("/quartz.properties"));
+		factory.setQuartzProperties(quartzProperties);
+		return factory;
+	}
+
+	@Bean
+	public Properties quartzProperties() throws IOException {
+		PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
+		propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
+		// 在quartz.properties中的属性被读取并注入后再初始化对象
+		propertiesFactoryBean.afterPropertiesSet();
+		return propertiesFactoryBean.getObject();
+	}
+}

+ 66 - 0
src/main/java/com/persagy/constant/RedisConstant.java

@@ -0,0 +1,66 @@
+package com.persagy.constant;
+
+/**
+ * @description: redis常量
+ * @author: fenghanchao
+ * @company: Persagy Technology Co.,Ltd
+ * @since: 2020/7/20 10:29
+ * @version: V1.0
+ **/
+public class RedisConstant {
+
+    /**
+     * 根据图类型获取数据平台图实例
+     * 格式为Hash格式  "DATA_PLATFORM_GRAPH_INSTANCE:projectId" : {"图类型":"图实例"}
+     */
+    public static final String DATA_PLATFORM_GRAPH_INSTANCE_KEY = "DATA_PLATFORM_GRAPH_INSTANCE";
+
+    /**
+     * 根据信息点获取表号功能号
+     * 格式为Hash格式 "INFO_CODE_METER_FUC:projectId":{"信息点":"表号-功能号"}
+     */
+    public static final String INFO_CODE_METER_FUC_KEY = "INFO_CODE_METER_FUC";
+
+    /**
+     * 根据表号功能号获取信息点
+     * 格式为Hash格式
+     */
+    public static final String METER_FUC_INFO_CODE_KEY = "METER_FUC_INFO_CODE";
+
+    /**
+     * 根据表号功能号获取实时数据
+     * 格式为Hash格式 "METER_FUNC_CURRENT_DATA:projectId" : {"表号-功能号" : 实时数据值}
+     */
+    public static final String METER_FUNC_CURRENT_DATA_KEY = "METER_FUNC_CURRENT_DATA";
+
+    /**
+     * 根据报警定义id获取报警定义详情
+     * 格式为Hash格式 "ALARM_DEFINITION_DETAIL:projectId" : {"报警定义id" : "报警定义详情"}
+     */
+    public static final String ALARM_DEFINITION_DETAIL_KEY = "ALARM_DEFINITION_DETAIL";
+
+    /**
+     * 根据表号功能号获取多条报警定义id
+     * 格式为Hash格式 "ALARM_DEFINITION_DETAIL:project_id" : {"表号-功能号" : "['报警定义id1','报警定义id2']"}
+     */
+    public static final String ALARM_METER_FUNC_DEFINITION_KEY = "ALARM_METER_FUNC_DEFINITION";
+
+    /**
+     * 根据报警定义id获取对应的表号功能号
+     * 格式为Hash格式 "ALARM_DEFINITION_METER_FUNC:projectId" : {"报警定义id" : "['表号-功能号1','表号-功能号2']"}
+     */
+    public static final String ALARM_DEFINITION_METER_FUNC_KEY = "ALARM_DEFINITION_METER_FUNC";
+
+    /**
+     * 根据报警定义id获取初步满足报警条件的预报警时间
+     * 格式为Hash格式 "ALARM_DEFINITION_PRE_ALARM:projectId" : {"报警定义id" : "2020-07-20 01:00:00"}
+     */
+    public static final String ALARM_DEFINITION_PRE_ALARM_KEY = "ALARM_DEFINITION_PRE_ALARM";
+
+    /**
+     * 根据报警定义id获取报警详情
+     * 格式为Hash格式 "ALARM_DEFINITION_ALARM:projectId" : {"报警定义id" : "{报警定义详情}"}
+     */
+    public static final String ALARM_DEFINITION_ALARM_INFO_KEY = "ALARM_DEFINITION_ALARM_INFO";
+
+}

+ 15 - 0
src/main/java/com/persagy/controller/HelloWorld.java

@@ -0,0 +1,15 @@
+package com.persagy.controller;
+
+
+import com.googlecode.aviator.AviatorEvaluator;
+
+public class HelloWorld {
+	public static void main(String[] args) {
+
+
+//		Boolean result2 = (Boolean) AviatorEvaluator.execute("3>1 && 2!=4 || true");
+		Boolean result2 = (Boolean) AviatorEvaluator.execute("3>1 && !!(2!=4)");
+		System.out.println(result2);//true
+	}
+}
+

+ 140 - 0
src/main/java/com/persagy/demo/netty/websocket/client/WebSocketClient.java

@@ -0,0 +1,140 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.persagy.demo.netty.websocket.client;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.websocketx.*;
+import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.WebSocketHttpHeaders;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.net.URI;
+
+/**
+ * This is an example of a WebSocket client.
+ * <p>
+ * In order to run this example you need a compatible WebSocket server.
+ * Therefore you can either start the WebSocket server from the examples
+ * by running {@link io.netty.example.http.websocketx.server}
+ * or connect to an existing WebSocket server such as
+ * <a href="http://www.websocket.org/echo.html">ws://echo.websocket.org</a>.
+ * <p>
+ * The client will attempt to connect to the URI passed to it as the first argument.
+ * You don't have to specify any arguments if you want to connect to the example WebSocket server,
+ * as this is the default.
+ * 对于客户端不是netty创建的websocket,连接时候回报错Invalid challenge,暂时没有找到解决办法
+ */
+public final class WebSocketClient implements org.springframework.web.socket.client.WebSocketClient {
+
+//    static final String URL = System.getProperty("url", "ws://127.0.0.1:9981/websocket");
+    static final String URL = System.getProperty("url", "ws://121.40.165.18:8800");
+
+    public static void main(String[] args) throws Exception {
+        URI uri = new URI(URL);
+        String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
+        final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
+        final int port;
+        if (uri.getPort() == -1) {
+            if ("ws".equalsIgnoreCase(scheme)) {
+                port = 80;
+            } else if ("wss".equalsIgnoreCase(scheme)) {
+                port = 443;
+            } else {
+                port = -1;
+            }
+        } else {
+            port = uri.getPort();
+        }
+
+        if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
+            System.err.println("Only WS(S) is supported.");
+            return;
+        }
+
+        final boolean ssl = "wss".equalsIgnoreCase(scheme);
+        final SslContext sslCtx;
+        if (ssl) {
+            sslCtx = SslContextBuilder.forClient()
+                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
+        } else {
+            sslCtx = null;
+        }
+
+        EventLoopGroup group = new NioEventLoopGroup();
+        try {
+            // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
+            // If you change it to V00, ping is not supported and remember to change
+            // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
+            final WebSocketClientHandler handler =
+                    new WebSocketClientHandler(
+                            WebSocketClientHandshakerFactory.newHandshaker(
+                                    uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
+
+
+            Bootstrap b = new Bootstrap();
+            b.group(group)
+             .channel(NioSocketChannel.class)
+             .handler(new ChannelInitializer<SocketChannel>() {
+                 @Override
+                 protected void initChannel(SocketChannel ch) {
+                     ChannelPipeline p = ch.pipeline();
+                     if (sslCtx != null) {
+                         p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
+                     }
+                     p.addLast(
+                             new HttpClientCodec(),
+                             new HttpObjectAggregator(8192),
+                             WebSocketClientCompressionHandler.INSTANCE,
+                             handler);
+                 }
+             });
+
+            Channel ch = b.connect(uri.getHost(), port).sync().channel();
+            handler.handshakeFuture().sync();
+            String msg = "console.readLine!";
+            WebSocketFrame frame = new TextWebSocketFrame(msg);
+            ch.writeAndFlush(msg);
+            ch.closeFuture().sync();
+        } finally {
+            group.shutdownGracefully();
+        }
+    }
+
+    @Override
+    public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler, String s, Object... objects) {
+        return null;
+    }
+
+    @Override
+    public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler, WebSocketHttpHeaders webSocketHttpHeaders, URI uri) {
+        return null;
+    }
+}

+ 147 - 0
src/main/java/com/persagy/demo/netty/websocket/client/WebSocketClientHandler.java

@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+//The MIT License
+//
+//Copyright (c) 2009 Carl Bystršm
+//
+//Permission is hereby granted, free of charge, to any person obtaining a copy
+//of this software and associated documentation files (the "Software"), to deal
+//in the Software without restriction, including without limitation the rights
+//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//copies of the Software, and to permit persons to whom the Software is
+//furnished to do so, subject to the following conditions:
+//
+//The above copyright notice and this permission notice shall be included in
+//all copies or substantial portions of the Software.
+//
+//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//THE SOFTWARE.
+
+package com.persagy.demo.netty.websocket.client;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.*;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.websocketx.*;
+import io.netty.util.CharsetUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
+
+    private final WebSocketClientHandshaker handshaker;
+    private ChannelPromise handshakeFuture;
+
+    public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
+        this.handshaker = handshaker;
+    }
+
+    public ChannelFuture handshakeFuture() {
+        return handshakeFuture;
+    }
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) {
+        handshakeFuture = ctx.newPromise();
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        handshaker.handshake(ctx.channel());
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        System.out.println("WebSocket Client disconnected!");
+    }
+
+    @Override
+    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
+        Channel ch = ctx.channel();
+
+        try {
+            readMsg(msg, ctx);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void readMsg(Object msg, ChannelHandlerContext ctx) {
+        Channel ch = ctx.channel();
+//        if ((msg instanceof FullHttpResponse)) {
+//            ctx.fireChannelRead(msg);
+//            return;
+//        }
+//        FullHttpResponse x = (FullHttpResponse) msg;
+//        System.out.println("---x---------:" + x);
+//        ByteBuf buf = (ByteBuf) msg;
+//        //创建目标大小的数组
+//        byte[] barray = new byte[buf.readableBytes()];
+//        //把数据从bytebuf转移到byte[]
+//        buf.getBytes(0, barray);
+//        //将byte[]转成字符串用于打印
+//        String str = new String(barray);
+//        System.out.println("------------:" + str);
+//        if (true) {
+//            return;
+//        }
+        if (!handshaker.isHandshakeComplete()) {
+            try {
+                handshaker.finishHandshake(ch, (FullHttpResponse) msg);
+                System.out.println("WebSocket Client connected!");
+                handshakeFuture.setSuccess();
+            } catch (WebSocketHandshakeException e) {
+                log.error("WebSocket Client failed to connect",e);
+                handshakeFuture.setFailure(e);
+            }
+            return;
+        }
+
+        if (msg instanceof FullHttpResponse) {
+            FullHttpResponse response = (FullHttpResponse) msg;
+            throw new IllegalStateException(
+                    "Unexpected FullHttpResponse (getStatus=" + response.status() +
+                            ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
+        }
+
+        WebSocketFrame frame = (WebSocketFrame) msg;
+//        String clientMsg = "console.readLine!";
+//        WebSocketFrame clientFrame = new TextWebSocketFrame(clientMsg);
+        if (frame instanceof TextWebSocketFrame) {
+            TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
+            System.out.println("WebSocket Client received message: " + textFrame.text());
+        } else if (frame instanceof PongWebSocketFrame) {
+            System.out.println("WebSocket Client received pong");
+        } else if (frame instanceof CloseWebSocketFrame) {
+            System.out.println("WebSocket Client received closing");
+            ch.close();
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        cause.printStackTrace();
+        if (!handshakeFuture.isDone()) {
+            handshakeFuture.setFailure(cause);
+        }
+        ctx.close();
+    }
+}

+ 17 - 0
src/main/java/com/persagy/demo/netty/websocket/server/MyChannelHandlerPool.java

@@ -0,0 +1,17 @@
+package com.persagy.demo.netty.websocket.server;
+
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
+/**
+ * MyChannelHandlerPool
+ * 通道组池,管理所有websocket连接
+ */
+public class MyChannelHandlerPool {
+
+    public MyChannelHandlerPool(){}
+
+    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
+
+}

+ 87 - 0
src/main/java/com/persagy/demo/netty/websocket/server/MyWebSocketHandler.java

@@ -0,0 +1,87 @@
+package com.persagy.demo.netty.websocket.server;
+
+import com.alibaba.fastjson.JSON;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * MyWebSocketHandler
+ * WebSocket处理器,处理websocket连接相关
+ */
+public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        System.out.println("与客户端建立连接,通道开启!");
+
+        //添加到channelGroup通道组
+        MyChannelHandlerPool.channelGroup.add(ctx.channel());
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        System.out.println("与客户端断开连接,通道关闭!");
+        //添加到channelGroup 通道组
+        MyChannelHandlerPool.channelGroup.remove(ctx.channel());
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        //首次连接是FullHttpRequest,处理参数 by zhengkai.blog.csdn.net
+        if (null != msg && msg instanceof FullHttpRequest) {
+            FullHttpRequest request = (FullHttpRequest) msg;
+            String uri = request.uri();
+
+            Map paramMap=getUrlParams(uri);
+            System.out.println("接收到的参数是:"+JSON.toJSONString(paramMap));
+            //如果url包含参数,需要处理
+            if(uri.contains("?")){
+                String newUri=uri.substring(0,uri.indexOf("?"));
+                System.out.println(newUri);
+                request.setUri(newUri);
+            }
+
+        }else if(msg instanceof TextWebSocketFrame){
+            //正常的TEXT消息类型
+            TextWebSocketFrame frame=(TextWebSocketFrame)msg;
+            System.out.println("客户端收到服务器数据:" +frame.text());
+            sendAllMessage(frame.text());
+        }
+        super.channelRead(ctx, msg);
+    }
+
+    @Override
+    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
+
+    }
+
+    private void sendAllMessage(String message){
+        //收到信息后,群发给所有channel
+        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(message));
+    }
+
+    private static Map getUrlParams(String url){
+        Map<String,String> map = new HashMap<>();
+        url = url.replace("?",";");
+        if (!url.contains(";")){
+            return map;
+        }
+        if (url.split(";").length > 0){
+            String[] arr = url.split(";")[1].split("&");
+            for (String s : arr){
+                String key = s.split("=")[0];
+                String value = s.split("=")[1];
+                map.put(key,value);
+            }
+            return  map;
+
+        }else{
+            return map;
+        }
+    }
+}

+ 59 - 0
src/main/java/com/persagy/demo/netty/websocket/server/NettyServer.java

@@ -0,0 +1,59 @@
+package com.persagy.demo.netty.websocket.server;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+
+
+/**
+ * NettyServer Netty服务器配置
+ * https://github.com/moshowgame/spring-cloud-study.git
+ */
+public class NettyServer {
+    private final int port;
+
+    public NettyServer(int port) {
+        this.port = port;
+    }
+
+    public void start() throws Exception {
+        EventLoopGroup bossGroup = new NioEventLoopGroup();
+
+        EventLoopGroup group = new NioEventLoopGroup();
+        try {
+            ServerBootstrap sb = new ServerBootstrap();
+            sb.option(ChannelOption.SO_BACKLOG, 1024);
+            sb.group(group, bossGroup) // 绑定线程池
+                    .channel(NioServerSocketChannel.class) // 指定使用的channel
+                    .localAddress(this.port)// 绑定监听端口
+                    .childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作
+                        @Override
+                        protected void initChannel(SocketChannel ch) throws Exception {
+                            System.out.println("收到新连接");
+                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
+                            ch.pipeline().addLast(new HttpServerCodec());
+                            //以块的方式来写的处理器
+                            ch.pipeline().addLast(new ChunkedWriteHandler());
+                            ch.pipeline().addLast(new HttpObjectAggregator(8192));
+                            ch.pipeline().addLast(new MyWebSocketHandler());
+                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/websocket", null, true, 65536 * 10));
+                        }
+                    });
+            ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
+            System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
+            cf.channel().closeFuture().sync(); // 关闭服务器通道
+        } finally {
+            group.shutdownGracefully().sync(); // 释放线程池资源
+            bossGroup.shutdownGracefully().sync();
+        }
+    }
+}

+ 89 - 0
src/main/java/com/persagy/entity/AlarmDefine.java

@@ -0,0 +1,89 @@
+package com.persagy.entity;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+/**
+*@description:
+*@author:LuoGuangyi
+*@company:PersagyTechnologyCo.,Ltd
+*@since:2020/10/18 001814:37
+*@version:V1.0
+ *{
+ *     "streamId": "",
+ *     "opCode": "",
+ *     "content": [
+ *         {
+ *             "id": 1,
+ *             "objId": "obj1",
+ *             "itemCode": "1055",
+ *             "projectId": "xxx",
+ *             "name": "室内高温",
+ *             "dealFunc": "point",
+ *             "category": "环境报警",
+ *             "level": "B",
+ *             "remark": "我是备注哈",
+ *             "open": 1,
+ *             "condition": {
+ *                 "infoCode": [
+ *                     "$infoCode1",
+ *                     "$infoCode1"
+ *                 ],
+ *                 "infoCodeId": [
+ *                     {
+ *                         "infoCode": "infoCode1",
+ *                         "meterId": "11",
+ *                         "funcId": "12"
+ *                     },
+ *                     {
+ *                         "infoCode": "infoCode2",
+ *                         "meterId": "21",
+ *                         "funcId": "22"
+ *                     }
+ *                 ],
+ *                 "trigger": "($infoCode1 > 10 && $infoCode1 < 20) && ($infoCode2 < 20)",
+ *                 "end": "!($infoCode1 < 20 && $infoCode1 > 10)",
+ *                 "$triggerUphold": 10,
+ *                 "$endUphold": 5,
+ *                 "$time": {
+ *                     "type": "period",
+ *                     "period": {
+ *                         "$gt": "090000",
+ *                         "$lt": "210000"
+ *                     }
+ *                 }
+ *             }
+ *         }
+ *     ]
+ * }
+ *
+**/
+@Data
+public class AlarmDefine {
+    /**
+     * 报警定义ID
+     */
+    private Long id;
+    /**
+     * 报警对象ID
+     */
+    private String objId;
+    /**
+     * 报警条目编码
+     */
+    private String itemCode;
+    /**
+     * 报警名称
+     */
+    private String name;
+
+    /**
+     * 信息点详情
+     */
+    private Condition condition;
+
+    @Override
+    public String toString() {
+        return JSONObject.toJSONString(this);
+    }
+}

+ 154 - 0
src/main/java/com/persagy/entity/AlarmRecord.java

@@ -0,0 +1,154 @@
+package com.persagy.entity;
+
+import com.alibaba.fastjson.JSONObject;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 报警记录
+ */
+@ApiModel(value = "报警记录")
+@Data
+@NoArgsConstructor
+@Builder
+public class AlarmRecord implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    /**
+     * 主键
+     */
+    @ApiModelProperty(value = "主键")
+    private String id;
+
+    /**
+     * 报警条目编码
+     */
+    @ApiModelProperty(value = "报警条目编码")
+    private String itemCode;
+
+    /**
+     * 项目id
+     */
+    @ApiModelProperty(value = "项目id")
+    private String projectId;
+
+    /**
+     * 对象id
+     */
+    @ApiModelProperty(value = "对象id")
+    private String objId;
+
+    /**
+     * 严重程度
+     */
+    @ApiModelProperty(value = "严重程度")
+    private String level;
+
+    /**
+     * 报警描述
+     */
+    @ApiModelProperty(value = "报警描述")
+    private String remark;
+
+    /**
+     * 创建用户
+     */
+    @ApiModelProperty(value = "创建用户")
+    private String createUser;
+
+    /**
+     * 创建时间 时间戳
+     */
+    @ApiModelProperty(value = "创建时间")
+    private Long createTime;
+
+    /**
+     * 更新用户
+     */
+    @ApiModelProperty(value = "更新用户")
+    private String updateUser;
+
+    /**
+     * 更新时间
+     */
+    @ApiModelProperty(value = "更新时间")
+    private Date updateTime;
+
+    /**
+     * 删除标记 1:true,0:false
+     */
+    @ApiModelProperty(value = "删除标记 1:true,0:false")
+    private Integer valid;
+
+    /**
+     * 报警名称
+     */
+    @ApiModelProperty(value = "报警名称")
+    private String name;
+
+    /**
+     * 报警分类
+     */
+    @ApiModelProperty(value = "报警分类")
+    private String category;
+
+    /**
+     * 是否重点关注
+     */
+    @ApiModelProperty(value = "是否重点关注")
+    private Integer concern;
+
+    /**
+     * 报警状态
+     */
+    @ApiModelProperty(value = "报警状态")
+    private Integer state;
+
+    /**
+     * 有效期开始时间
+     */
+    @ApiModelProperty(value = "有效期开始时间")
+    private Date effectStartTime;
+
+    /**
+     * 有效期结束时间
+     */
+    @ApiModelProperty(value = "有效期结束时间")
+    private Date effectEndTime;
+
+    /**
+     * 报警性质
+     */
+    @ApiModelProperty(value = "报警性质")
+    private Integer nature;
+
+    /**
+     * 处理方式
+     */
+    @ApiModelProperty(value = "处理方式")
+    private Integer treatMode;
+
+    /**
+     * 处理状态
+     */
+    @ApiModelProperty(value = "处理状态")
+    private Integer treatState;
+
+    /**
+     * 集团编码
+     */
+    @ApiModelProperty(value = "集团编码")
+    private String groupCode;
+
+    @Override
+    public String toString() {
+        return JSONObject.toJSONString(this);
+    }
+
+}

+ 138 - 0
src/main/java/com/persagy/entity/AlarmState.java

@@ -0,0 +1,138 @@
+package com.persagy.entity;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+/**
+ * @description:报警时间状态
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/20 002016:30
+ * @version:V1.0
+ **/
+@Data
+public class AlarmState {
+    /**
+     * 报警定义ID
+     */
+    private String definitionId;
+    /**
+     * 报警状态( 0-正常 1-报警)
+     */
+    private String state = "0";
+    /**
+     * 最近下一次数据正常状态
+     */
+    private boolean latestDataNormalstate = true;
+    /**
+     * 是否过期(0-未过期,1-已过期)
+     */
+    private String expire = "0";
+    /**
+     * 报警开始时间
+     */
+    private String alarmStartTime = "";
+    /**
+     * 报警恢复开始时间
+     */
+    private String alarmEndTime = "";
+
+    public AlarmState(String definitionId) {
+        this.definitionId = definitionId;
+    }
+
+    @Override
+    public String toString() {
+        return JSONObject.toJSONString(this);
+    }
+
+    /**
+     * 重置除了实时数据正常状态外的数据为默认值
+     * (不报警,不过期,报警开始时间和结束时间为空)
+     * @return
+     */
+    public String reset() {
+        this.setState("0");
+        this.setExpire("0");
+        this.setAlarmStartTime("");
+        this.setExpire("");
+        return JSONObject.toJSONString(this);
+    }
+
+    public enum Overdue {
+
+        NORMAL("0", "正常"),
+        OVERDUE("1", "已过期");
+
+        private String type;
+        private String name;
+
+        Overdue(String type, String name) {
+            this.type = type;
+            this.name = name;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public static Overdue getEnumByType(Integer type) {
+            for (Overdue status : Overdue.values()) {
+                if (status.getType().equals(type)) {
+                    return status;
+                }
+            }
+            return null;
+        }
+    }
+    public enum State {
+
+        NORMAL("0", "正常、已恢复"),
+        NOT_DEAL("1", "异常、未处理");
+
+        private String type;
+        private String name;
+
+        State(String type, String name) {
+            this.type = type;
+            this.name = name;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public static Overdue getEnumByType(Integer type) {
+            for (Overdue status : Overdue.values()) {
+                if (status.getType().equals(type)) {
+                    return status;
+                }
+            }
+            return null;
+        }
+    }
+}

+ 58 - 0
src/main/java/com/persagy/entity/Condition.java

@@ -0,0 +1,58 @@
+package com.persagy.entity;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @description:报警定义条件
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/20 002018:20
+ * @version:V1.0
+ * 实例:
+ * {
+ *                 "infoCode": [
+ *                     "infoCode1",
+ *                     "infoCode1"
+ *                 ],
+ *                 "infoCodes": [
+ *                     {
+ *                         "infoCode": "infoCode1",
+ *                         "meterId": "11",
+ *                         "funcId": "12"
+ *                     },
+ *                     {
+ *                         "infoCode": "infoCode2",
+ *                         "meterId": "21",
+ *                         "funcId": "22"
+ *                     }
+ *                 ],
+ *                 "trigger": "(infoCode1 > 10 && infoCode1 < 20) && (infoCode2 < 20)",
+ *                 "end": "!(infoCode1 < 20 && infoCode1 > 10)",
+ *                 "$triggerUphold": 10,
+ *                 "$endUphold": 5,
+ *                 "$time": {
+ *                     "type": "period",
+ *                     "period": {
+ *                         "$gt": "090000",
+ *                         "$lt": "210000"
+ *                     }
+ *                 }
+ *             }
+ **/
+@Data
+public class Condition {
+    List<String> infoCode;
+    List<JSONObject> infoCodes;
+    private String trigger;
+    private String end;
+    private int triggerUphold;
+    private int endUphold;
+    private JSONObject time;
+    @Override
+    public String toString() {
+        return JSONObject.toJSONString(this);
+    }
+}

+ 14 - 3
src/main/java/com/persagy/entity/NettyMessage.java

@@ -11,7 +11,7 @@ import java.util.List;
 @Getter
 @Setter
 @NoArgsConstructor
-public class NettyMessage {
+public class NettyMessage<T> {
     /*
     唯一标识
      */
@@ -20,7 +20,9 @@ public class NettyMessage {
     @JSONField()
     private int version = 1;
     /*
-    操作类型编码  1-请求  2 -响应  3 通知
+    操作类型编码  1-请求、2 -响应、3-通知、4-边缘端获取报警定义、
+                5-边缘端主动推送报警记录、6-边缘端主动更新报警记录状态、7-云端推送修改的报警定义给边缘端
+                8-云端把报警记录的id推送到边缘端
      */
     @JSONField()
     private int opCode;
@@ -39,12 +41,21 @@ public class NettyMessage {
     传输内容
      */
     @JSONField(jsonDirect=true)
-    private List<CommandResult> content;
+    private List<T> content;
     /*
     成功标识
      */
     @JSONField()
     private Boolean success;
+
+    public List<T> getContent() {
+        return content;
+    }
+
+    public void setContent(List<T> content) {
+        this.content = content;
+    }
+
     @Override
     public String toString() {
         return JSONObject.toJSONString(this);

+ 9 - 6
src/main/java/com/persagy/init/InitRunner.java

@@ -1,9 +1,11 @@
 package com.persagy.init;
 
 
+import com.googlecode.aviator.AviatorEvaluator;
+import com.persagy.client.WebSocketClientFactory;
+import com.persagy.demo.netty.websocket.server.NettyServer;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Service;
@@ -14,17 +16,18 @@ import org.springframework.stereotype.Service;
 @Slf4j
 public class InitRunner implements CommandLineRunner {
 
-
+	@Autowired
+	private WebSocketClientFactory webSocketClientFactory;
 //	@Autowired
 //	private GroupNettyClient groupNettyClient;
-//
-//	@Autowired
-//	private TerminalClient terminalClient;
+
 
 
 	@Override
 	public void run(String... args) throws Exception {
+		//5.0 开始引入了 LRU 缓存,可指定缓存的表达式个数,比如设置为最大 1 万个缓存结果:
+		AviatorEvaluator.getInstance().useLRUExpressionCache(10000);
+		webSocketClientFactory.retryOutCallWebSocketClient();
 //		groupNettyClient.start();
-//		terminalClient.start();
 	}
 }

+ 53 - 0
src/main/java/com/persagy/job/AlarmExpireJob.java

@@ -0,0 +1,53 @@
+package com.persagy.job;
+
+import com.alibaba.fastjson.JSONObject;
+import com.persagy.client.GroupNettyClient;
+import com.persagy.entity.AlarmState;
+import com.persagy.entity.CommandResult;
+import com.persagy.entity.NettyMessage;
+import com.persagy.utils.DateUtils;
+import com.persagy.utils.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.quartz.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+@DisallowConcurrentExecution
+@PersistJobDataAfterExecution
+@Slf4j
+public class AlarmExpireJob extends QuartzJobBean {
+
+    @Autowired
+    private GroupNettyClient groupNettyClient;
+
+    private String alarmStateString;
+
+    private AtomicLong nums = new AtomicLong(1L);
+
+    @Override
+    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
+        try {
+            JobDataMap mergedJobDataMap = context.getMergedJobDataMap();
+            log.info("------------------");
+            if(StringUtils.isNotBlank(alarmStateString)) {
+                AlarmState alarmState = StringUtil.tranferItemToDTO(this.alarmStateString, AlarmState.class);
+                log.info("报警恢复参数为:",alarmState.toString());
+//                groupNettyClient.sendMessage();
+            }
+        } catch (Exception e) {
+            log.error("job hander error",e);
+        }
+
+    }
+
+    public String getAlarmStateString() {
+        return alarmStateString;
+    }
+
+    public void setAlarmStateString(String alarmStateString) {
+        this.alarmStateString = alarmStateString;
+    }
+}

+ 14 - 0
src/main/java/com/persagy/service/AlarmDefineService.java

@@ -0,0 +1,14 @@
+package com.persagy.service;
+
+import java.util.List;
+
+/**
+ * @description:报警定义逻辑接口类(项目报警定义全量获取,报警定义添加修改删除同步更新)
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/15 11:22
+ * @version:V1.0
+ **/
+public interface AlarmDefineService {
+    List listAlarmDefine();
+}

+ 11 - 0
src/main/java/com/persagy/service/AlarmHandleService.java

@@ -0,0 +1,11 @@
+package com.persagy.service;
+/**
+ * @description: 报警处理接口:包含报警产生和报警恢复
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/15 11:03
+ * @version:V1.0
+ **/
+public interface AlarmHandleService {
+    void handleIOTData(String msg);
+}

+ 11 - 0
src/main/java/com/persagy/service/AlarmQuartzService.java

@@ -0,0 +1,11 @@
+package com.persagy.service;
+
+/**
+ * @description:报警定时任务接口
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/21 002118:58
+ * @version:V1.0
+ **/
+public interface AlarmQuartzService {
+}

+ 86 - 0
src/main/java/com/persagy/service/impl/AlarmDefineServiceImpl.java

@@ -0,0 +1,86 @@
+package com.persagy.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.persagy.cache.AlarmInfoCache;
+import com.persagy.entity.AlarmDefine;
+import com.persagy.entity.NettyMessage;
+import com.persagy.service.AlarmDefineService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @description:报警定义逻辑实现类(项目报警定义全量获取,报警定义添加修改删除同步更新)
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/15 11:24
+ * @version:V1.0
+ **/
+@Service
+public class AlarmDefineServiceImpl implements AlarmDefineService {
+    @Autowired
+    AlarmInfoCache alarmInfoCache;
+
+    @Override
+    public List listAlarmDefine() {
+        String ad = "{\n" +
+                "    \"streamId\": \"\",\n" +
+                "    \"opCode\": \"\",\n" +
+                "    \"content\": [\n" +
+                "        {\n" +
+                "            \"id\": 1,\n" +
+                "            \"objId\": \"obj1\",\n" +
+                "            \"itemCode\": \"1055\",\n" +
+                "            \"projectId\": \"xxx\",\n" +
+                "            \"name\": \"室内高温\",\n" +
+                "            \"dealFunc\": \"point\",\n" +
+                "            \"category\": \"环境报警\",\n" +
+                "            \"level\": \"B\",\n" +
+                "            \"remark\": \"我是备注哈\",\n" +
+                "            \"open\": 1,\n" +
+                "            \"condition\": {\n" +
+                "                \"infoCode\": [\n" +
+                "                    \"$infoCode1\",\n" +
+                "                    \"$infoCode1\"\n" +
+                "                ],\n" +
+                "                \"infoCodeId\": [\n" +
+                "                    {\n" +
+                "                        \"infoCode\": \"infoCode1\",\n" +
+                "                        \"meterId\": \"11\",\n" +
+                "                        \"funcId\": \"12\"\n" +
+                "                    },\n" +
+                "                    {\n" +
+                "                        \"infoCode\": \"infoCode2\",\n" +
+                "                        \"meterId\": \"21\",\n" +
+                "                        \"funcId\": \"22\"\n" +
+                "                    }\n" +
+                "                ],\n" +
+                "                \"trigger\": \"($infoCode1 > 10 && $infoCode1 < 20) && ($infoCode2 < 20)\",\n" +
+                "                \"end\": \"!($infoCode1 < 20 && $infoCode1 > 10)\",\n" +
+                "                \"$triggerUphold\": 10,\n" +
+                "                \"$endUphold\": 5,\n" +
+                "                \"$time\": {\n" +
+                "                    \"type\": \"period\",\n" +
+                "                    \"period\": {\n" +
+                "                        \"$gt\": \"090000\",\n" +
+                "                        \"$lt\": \"210000\"\n" +
+                "                    }\n" +
+                "                }\n" +
+                "            }\n" +
+                "        }\n" +
+                "    ]\n" +
+                "}";
+        NettyMessage<AlarmDefine> message = JSONObject.parseObject(ad,  new TypeReference<NettyMessage<AlarmDefine>>(){});
+        List<AlarmDefine> alarmDefineList =message.getContent();
+        alarmInfoCache.clearAllAlarmDefine();
+        for (AlarmDefine alarmDefine : alarmDefineList) {
+            alarmInfoCache.putAlarmDefinitionById(alarmDefine);
+        }
+        return alarmDefineList;
+    }
+
+
+
+}

+ 233 - 0
src/main/java/com/persagy/service/impl/AlarmHandleServiceImpl.java

@@ -0,0 +1,233 @@
+package com.persagy.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.googlecode.aviator.AviatorEvaluator;
+import com.googlecode.aviator.Expression;
+import com.persagy.cache.AlarmInfoCache;
+import com.persagy.cache.CurrentDataCache;
+import com.persagy.client.GroupNettyClient;
+import com.persagy.entity.*;
+import com.persagy.service.AlarmHandleService;
+import com.persagy.utils.DateUtils;
+import com.persagy.utils.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @description:报警处理实现类:包含报警产生和报警恢复
+ * @author:LuoGuanhyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/15 001511:14
+ * @version:V1.0
+ **/
+@Service
+@Slf4j
+public class AlarmHandleServiceImpl implements AlarmHandleService {
+    @Autowired
+    AlarmInfoCache alarmInfoCache;
+    @Autowired
+    CurrentDataCache currentDataCache;
+    @Autowired
+    GroupNettyClient groupNettyClient;
+    /**
+     * 集团编码
+     */
+    @Value("${group.code}")
+    public String groupCode;
+    /**
+     * 系统标识,用于创建人
+     */
+    @Value("${system.id}")
+    public String systemId;
+
+    @Override
+    public void handleIOTData(String msg) {
+        String[] split = msg.split(";");
+        if (split.length < 4) {
+            return;
+        }
+        //返回的信息点个数
+        int nums = (split.length) / 4;
+        System.out.println("length:"+split.length);
+        for (int i = 0; i < nums; i++) {
+            String dateTime = split[i * 4];
+            String meterId = split[i * 4 + 1];
+            String funcId = split[i * 4 + 2];
+            double value = Double.parseDouble(split[i * 4 + 3]);
+
+            if (alarmInfoCache.hasKey(meterId, funcId)) {
+                currentDataCache.putCurrentData(meterId, funcId, value);
+                List<AlarmDefine> alarmDefines = alarmInfoCache.getAlarmDefinitionIdByMeterFuncId(meterId, funcId);
+                for (AlarmDefine alarmDefine : alarmDefines) {
+                    Condition condition = alarmDefine.getCondition();
+                    List<JSONObject> codeDetail = condition.getInfoCodes();
+                    boolean match = codeDetail.stream().allMatch(p -> currentDataCache.hasKey(p.getString("meterId"), p.getString("funcId")));
+                    if (match) {
+                        String defineId = alarmInfoCache.getAlarmDefineId(alarmDefine);
+                        String trigger = condition.getTrigger();
+                        String end = condition.getEnd();
+                        HashMap paramMap = new HashMap<>();
+                        for (int j = 0; j < codeDetail.size(); j++) {
+                            JSONObject code = codeDetail.get(j);
+                            paramMap.put(code.getString("infoCode"), currentDataCache.getCurrentData(code.getString("meterId"), code.getString("funcId")));
+                        }
+                        Expression triggerExp = AviatorEvaluator.compile(trigger, true);
+                        Expression endExp = AviatorEvaluator.compile(end, true);
+                        Boolean triggerResult = (Boolean) triggerExp.execute(paramMap);
+                        Boolean endResult = (Boolean) endExp.execute(paramMap);
+                        System.out.println("triggerResult:" + triggerResult);
+                        System.out.println("endResult:" + endResult);
+                        AlarmState alarmState = alarmInfoCache.getAlarmState(defineId);
+                        if (Objects.isNull(alarmState)) {
+                            alarmState = new AlarmState(defineId);
+                        }
+                        //报警产生值满足(这里的满足不考虑报警持续时间)
+                        if (triggerResult) {
+                            //报警的时候不考虑报警恢复,因为同时报警和报警恢复是不应该出现的
+                            handlerNowDataAlarm(alarmDefine,alarmState, dateTime, condition, defineId);
+                        } else {
+                            //当前数据正常
+                            handlerNowDataNormal(alarmDefine, dateTime, condition, defineId, endResult, alarmState);
+                        }
+                    } else {
+                        log.warn("部分信息点没有数值:[{}]", codeDetail);
+                    }
+                }
+            }
+
+        }
+    }
+    /**
+     * @description:当前数据正常判断逻辑
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @param alarmDefine: 报警定义
+     * @param dateTime:IOT采集时间
+     * @param condition:报警条件
+     * @param defineId:报警定义ID
+     * @param endResult:报警恢复监测结果
+     * @param alarmState: 报警状态时间实体
+     * @return: void
+     * @since: 2020/10/21 16:46
+     * @version: V1.0
+     */
+    private void handlerNowDataNormal(AlarmDefine alarmDefine, String dateTime, Condition condition, String defineId, Boolean endResult, AlarmState alarmState) {
+        //当前数据正常,报警状态为正常:清空之前的报警计时,重置回默认状态
+        if (AlarmState.State.NORMAL.getType().equals(alarmState.getState())){
+            alarmState = new AlarmState(defineId);
+            alarmInfoCache.setAlarmState(defineId, alarmState);
+        }else if(AlarmState.State.NOT_DEAL.getType().equals(alarmState.getState())){
+            //报警状态异常时候
+            alarmState.setLatestDataNormalstate(true);
+            //报警恢复条件满足,判断是否满足报警恢复持续时间
+            if(endResult){
+                String endTime = alarmState.getAlarmEndTime();
+                if(StringUtil.isEmpty(endTime)){
+                    endTime = dateTime;
+                }
+                alarmState.setAlarmEndTime(endTime);
+                alarmState.setAlarmStartTime("");
+                //设置开始恢复时间
+                int uphold = condition.getEndUphold();
+                //超过报警恢复设置的持续时间
+                if(DateUtils.betweenTwoTimeSecond(endTime, dateTime)>=0){
+                    log.error("产生一条报警恢复消息");
+                    log.error("产生一条取消报警过期定时任务的消息");
+                    //报警恢复,报警状态重置回默认
+                    alarmState.reset();
+                }
+            }
+            alarmInfoCache.setAlarmState(defineId, alarmState);
+        }
+    }
+
+    /**
+     *
+     * @param alarmDefine 报警定义
+     * @param dateTime :IOT数据采集时间
+     * @param condition :报警定义条件
+     * @param defineId :报警定义ID
+     * @description:处理当前值报警的情况
+     * @exception:
+     * @author: LuoGuangyi
+     * @company: Persagy Technology Co.,Ltd
+     * @return: void
+     * @since: 2020/10/20 22:31
+     * @version: V1.0
+     */
+    private void handlerNowDataAlarm(AlarmDefine alarmDefine, AlarmState alarmState, String dateTime, Condition condition, String defineId) {
+        alarmState.setLatestDataNormalstate(false);
+        JSONObject time = condition.getTime();
+        boolean inEffectTime = false;
+        boolean hasExpire = true;
+        //生效时间段,有误过期时间判断
+        if (StringUtil.isEmpty(time) || StringUtil.isEmpty(time.getString("type"))) {
+            //无生效时间设置,则任何时间生效
+            inEffectTime = true;
+            //没有设置生效时间,所以也没有设置过期时间
+            hasExpire = false;
+
+        }else {
+            if("period".equals(time.getString("type")) && "0000".equals(time.getString("$gt").substring(0,4)) && "2359".equals(time.getString("$lt").substring(0,4))){
+                //架构师w确认 00:00-23:59的生效时间为一值生效,23:59分不过期
+                hasExpire = false;
+            }
+            if ("period".equals(time.getString("type")) && time.getString("$gt").compareTo(DateUtils.getSdfTimeNotDate(dateTime)) >= 0 && time.getString("$lt").compareTo(DateUtils.getSdfTimeNotDate(dateTime)) <= 0) {
+                //在生效时间
+                inEffectTime = true;
+            } else {
+                log.info("[{}]不在设置的生效时间段", dateTime);
+            }
+        }
+        if (inEffectTime) {
+            //之前是报警状态,现在还是报警,报警值没变化
+            //之前是是正常时候
+            if (AlarmState.State.NORMAL.getType().equals(alarmState.getState())) {
+                //设置开始报警时间
+                alarmState.setAlarmStartTime(dateTime);
+                long timeSecond = 0;
+                if (StringUtil.isNotEmpty(alarmState.getAlarmStartTime())) {
+                    timeSecond = DateUtils.betweenTwoTimeSecond(alarmState.getAlarmStartTime(), dateTime);
+                }
+                if (timeSecond >= condition.getTriggerUphold()) {
+                    log.error("大于持续时间了,产生一条报警");
+                    AlarmRecord alarmRecord = AlarmRecord.builder()
+                            .groupCode(groupCode)
+                            .itemCode(alarmDefine.getItemCode())
+                            .createUser(systemId)
+                            .createTime(DateUtils.getMilliByLocalDateTime(dateTime))
+                            .objId(alarmDefine.getObjId())
+                            .build();
+                    NettyMessage<AlarmRecord> nettyMessage = new NettyMessage<>();
+                    nettyMessage.setOpCode(1);
+                    nettyMessage.setContent(Arrays.asList(alarmRecord));
+                    //推送一条报警记录给远端
+                    groupNettyClient.sendMessage(nettyMessage.toString());
+                    alarmState.setState(AlarmState.State.NOT_DEAL.getType());
+                    //有过期时间,生成报警过期消息
+                    if(hasExpire && "period".equals(time.getString("type"))){
+                        //过期时间
+                        String expireTime = time.getString("$lt");
+                        log.error("产生一条定时过期报警消息");
+
+                    }
+                } else {
+                    alarmState.setState(AlarmState.State.NORMAL.getType());
+                }
+            }
+        }else {
+            //不在生效时间的段的产生报警,要保存最近一次的数值是否是是报警
+            //其他条件全部改成默认值(不报警,不过期,报警开始时间和结束时间为空)
+            alarmState.reset();
+        }
+        alarmInfoCache.setAlarmState(defineId, alarmState);
+    }
+}

+ 62 - 0
src/main/java/com/persagy/service/impl/AlarmQuartzServiceImpl.java

@@ -0,0 +1,62 @@
+package com.persagy.service.impl;
+
+import com.persagy.service.AlarmQuartzService;
+import lombok.extern.slf4j.Slf4j;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+/**
+ * @description:报警定时任务服务
+ * @author:LuoGuanhyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/9/18 001820:11
+ * @version:V1.0
+ **/
+@Slf4j
+@Service
+public class AlarmQuartzServiceImpl implements AlarmQuartzService {
+
+    @Autowired
+    @Qualifier("quartzScheduler")
+    Scheduler quartzScheduler;
+
+    /**
+     * 添加定时器
+     * @param startTime 添加报警恢复定时任务
+     * @param jobName   job名称
+     * @param jobGroupName  job分组
+     * @param jobDataMap  触发器的数据(任务相关的数据都放在这里)
+     * @return 
+     * @throws SchedulerException
+     */
+//    @Override
+//    public synchronized String addCommand(Date startTime, String jobName, String jobGroupName, JobDataMap jobDataMap) throws SchedulerException {
+//        if(startTime.before(new Date())){
+////            log.info("执行时间【{}】为历史时间,自动跳过",DateUtils.formatDate(startTime));
+//            return "2";
+//        }
+//        JobKey jobKey = new JobKey(jobName, jobGroupName);
+//        JobDetail jobDetail = JobBuilder.newJob(AlarmE.class).withIdentity(jobName, jobGroupName).build();
+//        Trigger trigger = TriggerBuilder.newTrigger().startAt(startTime)
+//                .withIdentity("trigger_" + jobName , jobName)
+//                .usingJobData(jobDataMap)
+//                .withSchedule(simpleSchedule().withMisfireHandlingInstructionIgnoreMisfires()).build();
+//        //已经存在的job实例和触发器自动覆盖 job实 例 唯 一标识:
+//        HashSet<Trigger> triggerSet = new HashSet<Trigger>();
+//        Date fireTime = trigger.getFireTimeAfter(new Date());
+//        if(fireTime != null){
+//            log.info("执行时间为:【{}】,设置时间为【{}】",DateUtils.formatDate(fireTime),DateUtils.formatDate(startTime));
+//        }else {
+//            log.info("执行时间为 空!");
+//        }
+//        triggerSet.add(trigger);
+//        quartzScheduler.scheduleJob(jobDetail, triggerSet, true);
+//
+//        return "success";
+//    }
+
+
+}

+ 61 - 0
src/main/java/com/persagy/service/impl/RunScriptExample.java

@@ -0,0 +1,61 @@
+package com.persagy.service.impl;
+
+/**
+ * @description:
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/17 001710:12
+ * @version:V1.0
+ **/
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.TimeInterval;
+import com.googlecode.aviator.AviatorEvaluator;
+import com.googlecode.aviator.Expression;
+import com.googlecode.aviator.Options;
+
+import java.util.Arrays;
+
+/**
+ * Run a script under examples folder.
+ *
+ * @author dennis(killme2008@gmail.com)
+ *
+ */
+public class RunScriptExample {
+
+    public static void main(final String[] args) throws Exception {
+        // You can try to test every script in examples folder by changing the file name.
+
+        TimeInterval timer = DateUtil.timer();
+        String expression = "$a-($b-$c) > 100";
+        String expression2 = "$a-($b-$c) > 200";
+        String expression3 = "$a-($b-$c) > 200";
+        Expression compiledExp = AviatorEvaluator.compile(expression,true);
+        Expression compiledExp2 = AviatorEvaluator.compile(expression2,true);
+        System.out.println(AviatorEvaluator.getInstance().getExpressionCacheSize());
+        Boolean result1 = (Boolean) compiledExp.execute(compiledExp.newEnv("$a", 100.3, "$b", 45, "$c", -199.100));
+        Boolean result2 = (Boolean) compiledExp.execute(compiledExp2.newEnv("$a", 100.3, "$b", 45, "$c", -199.100));
+
+        System.out.println(AviatorEvaluator.getInstance().getExpressionCacheSize());
+
+        Expression exp = AviatorEvaluator.getInstance().compileScript("statements.av",true);
+        System.out.println(AviatorEvaluator.getInstance().getExpressionCacheSize());
+        for (int i = 0; i < 10000; i++) {
+            Object result = exp.execute(exp.newEnv("$a", 100.3, "$b", 45, "$c", -199.100,"$list", Arrays.asList(1,3)));
+//            System.out.println(result);
+        }
+        //花费毫秒数
+        System.out.println(timer.interval());
+        timer.intervalRestart();
+        Expression compiledExp3 = AviatorEvaluator.compile(expression3,true);
+        for (int i = 0; i < 50000; i++) {
+            Boolean result3 = (Boolean) compiledExp.execute(compiledExp3.newEnv("$a", 100.3, "$b", i, "$c", -199.100));
+        }
+        System.out.println(timer.interval());
+//        String expression = "$a-($b-$c) > 100";
+//        Expression compiledExp = AviatorEvaluator.compile(expression);
+//        // Execute with injected variables.
+//        Boolean result = (Boolean) compiledExp.execute(compiledExp.newEnv("$a", 100.3, "$b", 45, "c", -199.100));
+//        System.out.println(result);
+    }
+}

+ 33 - 0
src/main/java/com/persagy/utils/DateUtils.java

@@ -30,6 +30,7 @@ public class DateUtils {
 	public static final String sdfDay = "yyyyMMdd";
 	public static final String sdfMonth = "yyyyMM";
 	public static final String sdfTime = "yyyyMMddHHmmss";
+	public static final String sdfTimeNotDate = "HHmmss";
 	public static final String sdfHour = "yyyyMMddHH";
 	public static final String sdfMinute = "yyyyMMddHHmm";
 	// 时间格式-显示
@@ -78,6 +79,15 @@ public class DateUtils {
 		return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
 	}
 
+	/**
+	 * 获取时间戳
+	 * @param dateTime
+	 * @return
+	 */
+	public static Long getMilliByLocalDateTime(String dateTime) {
+		return parse(dateTime).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+	}
+
 	// 获取指定日期的秒
 	public static Long getSecondsByLocalDateTime(LocalDateTime time) {
 		return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
@@ -172,6 +182,25 @@ public class DateUtils {
 
 		return field.between(startTime, endTime);
 	}
+	/**
+	 * 获取间隔时间
+	 * @param startTime
+	 * @param endTime
+	 * @return
+	 */
+	public static long betweenTwoTime(String startTime, String endTime, ChronoUnit field){
+		return betweenTwoTime(parse(startTime),parse(endTime),field);
+	}
+
+	/**
+	 * 获取间隔秒数
+	 * @param startTime
+	 * @param endTime
+	 * @return
+	 */
+	public static long betweenTwoTimeSecond(String startTime, String endTime){
+		return betweenTwoTime(startTime,endTime,ChronoUnit.SECONDS);
+	}
 
 	/**
 	 * Description:获得当前时间的字符串.
@@ -191,6 +220,9 @@ public class DateUtils {
 		LocalDateTime parse = parse(dateTime).minusHours(hour);
 		return format(parse);
 	}
+	public static String getSdfTimeNotDate(String dateTime) {
+		return dateTime.substring(8,14);
+	}
 
 	public static String getPlusHour(String dateTime, long hour) {
 		LocalDateTime parse = parse(dateTime).plusHours(hour);
@@ -269,6 +301,7 @@ public class DateUtils {
 
 	@SuppressWarnings("deprecation")
 	public static void main(String[] args) {
+		System.out.println(getSdfTimeNotDate(sdfTime));
 		LocalDateTime parse1 = DateUtils.parse("20150612152611");
 		LocalDateTime parse2 = DateUtils.parse("20150613152610");
 		System.out.println(betweenTwoTime(parse1, parse2, ChronoUnit.HOURS));

+ 45 - 0
src/main/java/com/persagy/utils/GZIPCompressUtil.java

@@ -0,0 +1,45 @@
+package com.persagy.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+public class GZIPCompressUtil {
+
+	public static String compress(String str) throws IOException {
+		if (str == null || str.length() == 0) {
+			return str;
+		}
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		GZIPOutputStream gzip = new GZIPOutputStream(out);
+		gzip.write(str.getBytes());
+		gzip.close();
+		return out.toString("ISO-8859-1");
+	}
+
+	public static String uncompress(String str) throws IOException {
+		String message = new String(GZIPCompressUtil.uncompress(str.getBytes("ISO-8859-1")));
+		return message;
+	}
+
+	public static byte[] uncompress(byte[] bytes) {
+		if (bytes == null || bytes.length == 0) {
+			return null;
+		}
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
+		try {
+			GZIPInputStream ungzip = new GZIPInputStream(in);
+			byte[] buffer = new byte[256];
+			int n;
+			while ((n = ungzip.read(buffer)) >= 0) {
+				out.write(buffer, 0, n);
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return out.toByteArray();
+	}
+}

+ 0 - 168
src/main/java/com/persagy/utils/IdWorker.java

@@ -1,168 +0,0 @@
-package com.persagy.utils;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.lang.management.ManagementFactory;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-
-/**
- * <p>名称:IdWorker.java</p>
- * <p>描述:分布式自增长ID</p>
- * <pre>
- *     Twitter的 Snowflake JAVA实现方案
- * </pre>
- * 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
- * 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
- * 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
- * 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
- * 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
- * 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
- * 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
- * <p>
- * 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
- *
- */
-@Component
-@Slf4j
-public class IdWorker {
-
-    // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)初始值为2019-09-04 15:50.可以使用69年
-    private final static long twepoch = 1586258613087L;
-    // 机器标识位数
-    private final static long workerIdBits = 4L;
-    // 数据中心标识位数
-    private final static long datacenterIdBits = 4L;
-    // 机器ID最大值
-    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
-    // 数据中心ID最大值
-    private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
-    // 毫秒内自增位
-    private final static long sequenceBits = 12L;
-    // 机器ID偏左移12位
-    private final static long workerIdShift = sequenceBits;
-    // 数据中心ID左移17位
-    private final static long datacenterIdShift = sequenceBits + workerIdBits;
-    // 时间毫秒左移22位
-    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
-
-    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
-    /* 上次生产id时间戳 */
-    private static long lastTimestamp = -1L;
-    // 0,并发控制
-    private long sequence = 0L;
-
-    private final long workerId;
-    // 数据标识id部分
-    private final long datacenterId;
-
-    public IdWorker(){
-        this.datacenterId = getDatacenterId(maxDatacenterId);
-        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
-    }
-    /**
-     * @param workerId
-     *            工作机器ID
-     * @param datacenterId
-     *            序列号
-     */
-    public IdWorker(long workerId, long datacenterId) {
-        if (workerId > maxWorkerId || workerId < 0) {
-            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
-        }
-        if (datacenterId > maxDatacenterId || datacenterId < 0) {
-            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
-        }
-        this.workerId = workerId;
-        this.datacenterId = datacenterId;
-    }
-    /**
-     * 获取下一个ID
-     *
-     * @return
-     */
-    public synchronized String nextId(String prefix) {
-        long timestamp = timeGen();
-        if (timestamp < lastTimestamp) {
-            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
-        }
-
-        if (lastTimestamp == timestamp) {
-            // 当前毫秒内,则+1
-            sequence = (sequence + 1) & sequenceMask;
-            if (sequence == 0) {
-                // 当前毫秒内计数满了,则等待下一秒
-                timestamp = tilNextMillis(lastTimestamp);
-            }
-        } else {
-            sequence = 0L;
-        }
-        lastTimestamp = timestamp;
-        // ID偏移组合生成最终的ID,并返回ID
-        long nextId = ((timestamp - twepoch) << timestampLeftShift)
-                | (datacenterId << datacenterIdShift)
-                | (workerId << workerIdShift) | sequence;
-
-        return prefix+Long.toString(nextId);
-    }
-
-    private long tilNextMillis(final long lastTimestamp) {
-        long timestamp = this.timeGen();
-        while (timestamp <= lastTimestamp) {
-            timestamp = this.timeGen();
-        }
-        return timestamp;
-    }
-
-    private long timeGen() {
-        return System.currentTimeMillis();
-    }
-
-    /**
-     * <p>
-     * 获取 maxWorkerId
-     * </p>
-     */
-    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
-        StringBuffer mpid = new StringBuffer();
-        mpid.append(datacenterId);
-        String name = ManagementFactory.getRuntimeMXBean().getName();
-        if (!name.isEmpty()) {
-         /*
-          * GET jvmPid
-          */
-            mpid.append(name.split("@")[0]);
-        }
-      /*
-       * MAC + PID 的 hashcode 获取16个低位
-       */
-        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
-    }
-
-    /**
-     * <p>
-     * 数据标识id部分
-     * </p>
-     */
-    protected static long getDatacenterId(long maxDatacenterId) {
-        long id = 0L;
-        try {
-            InetAddress ip = InetAddress.getLocalHost();
-            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
-            if (network == null) {
-                id = 1L;
-            } else {
-                byte[] mac = network.getHardwareAddress();
-                id = ((0x000000FF & (long) mac[mac.length - 1])
-                        | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
-                id = id % (maxDatacenterId + 1);
-            }
-        } catch (Exception e) {
-            log.error(" getDatacenterId: " + e.getMessage());
-        }
-        return id;
-    }
-
-
-}

+ 87 - 0
src/main/java/com/persagy/utils/LockUtil.java

@@ -0,0 +1,87 @@
+package com.persagy.utils;
+
+import cn.hutool.core.date.DateUtil;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @description:
+ * @author:LuoGuangyi
+ * @company:PersagyTechnologyCo.,Ltd
+ * @since:2020/10/19 00199:26
+ * @version:V1.0
+ **/
+@Service
+public class LockUtil {
+    static Lock lock = new ReentrantLock();
+    static Condition condition = lock.newCondition();
+    private static volatile boolean execute = true;
+    AtomicInteger num = new AtomicInteger(0);
+    public static boolean isExecute() {
+        return execute;
+    }
+
+    public static void setExecute(boolean execute) {
+        LockUtil.execute = execute;
+    }
+
+    public void await() throws InterruptedException {
+        System.out.println("----------");
+        ExecutorService executorService = Executors.newFixedThreadPool(5);
+        for (int j = 0; j < 20; j++) {
+            executorService.execute(()->{
+                for (int i = 0; i < 10000; i++) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    if(LockUtil.isExecute()){
+                        System.out.println(num.getAndIncrement());
+                    }else {
+                        lock.lock();
+                        try {
+                            System.out.println(DateUtil.now()+"等待");
+                            try {
+                                condition.await();
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                            System.out.println(DateUtil.now()+"["+Thread.currentThread().getName()+"]"+"等待执行"+num.getAndIncrement());
+                        }finally {
+                            lock.unlock();
+                        }
+
+                    }
+                }
+            });
+        }
+
+
+        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(()->{
+            lock.lock();
+            try {
+                LockUtil.setExecute(!LockUtil.isExecute());
+                //加个等待,保证正在执行的逻辑执行成功
+                Thread.sleep(2000);
+                if(LockUtil.isExecute()){
+                    condition.signalAll();
+                }
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            } finally {
+                lock.unlock();
+            }
+
+        },0,5, TimeUnit.SECONDS);
+    }
+
+}

+ 158 - 4
src/main/java/com/persagy/utils/StringUtil.java

@@ -4,11 +4,10 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.CollectionUtils;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.UUID;
+import java.lang.reflect.Array;
+import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -290,4 +289,159 @@ public class StringUtil {
         }
         return t.newInstance();
     }
+
+    public static String getStringValue(Object value, Object defaultValue) {
+        if (value == null) {
+            if (defaultValue != null) {
+                return defaultValue.toString();
+            }
+            return "";
+        }
+        return value.toString();
+    }
+
+    public static String getString(Object obj) {
+        return getString(obj, "");
+    }
+
+    public static String getString(Object obj, String defaultValue) {
+        return obj != null ? obj.toString() : defaultValue;
+    }
+
+    /**
+     * 判断字符串是否是整数
+     */
+    public static boolean isInteger(Object obj) {
+        try {
+            Integer.parseInt(String.valueOf(obj));
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取整数 ,不是则返回0
+     */
+    public static int getInt(Object obj) {
+        return getInt(obj, 0);
+    }
+
+    public static int getInt(Object obj, int defaultValue) {
+        return (obj != null) && (isInteger(obj)) ? Integer.parseInt(String.valueOf(obj)) : defaultValue;
+    }
+
+    /**
+     * 判断字符串是否是浮点数
+     */
+    public static boolean isLong(Object obj) {
+        try {
+            Long.parseLong(String.valueOf(obj));
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 判断字符串是否是数字
+     */
+    public static boolean isNumber(Object obj) {
+        return isInteger(obj) || isDouble(obj) || isLong(obj);
+    }
+
+    /**
+     * 获取长整数 ,不是长整数则返回0
+     */
+    public static long getLong(Object obj) {
+        return getLong(obj, 0L);
+    }
+
+    public static long getLong(Object obj, long defaultValue) {
+        return (obj != null) && (isLong(obj)) ? Long.parseLong(String.valueOf(obj)) : defaultValue;
+    }
+
+    /**
+     * 判断字符串是否是单精度浮点数
+     */
+    public static boolean isFloat(Object obj) {
+        try {
+            Float.parseFloat(String.valueOf(obj));
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取单精度浮点数
+     */
+    public static float getFloat(Object obj) {
+        return getFloat(obj, 0.0F);
+    }
+
+    public static float getFloat(Object obj, float defaultValue) {
+        return (obj != null) && (isFloat(obj)) ? Float.parseFloat(String.valueOf(obj)) : defaultValue;
+    }
+
+    /**
+     * 判断字符串是否是浮点数
+     */
+    public static boolean isDouble(Object obj) {
+        try {
+            Double.parseDouble(String.valueOf(obj));
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取双精度浮点数
+     */
+    public static double getDouble(Object obj) {
+        return getDouble(obj, 0.0D);
+    }
+
+    public static double getDouble(Object obj, double defaultValue) {
+        return (obj != null) && (isDouble(obj)) ? Double.parseDouble(String.valueOf(obj)) : defaultValue;
+    }
+
+    public static boolean isNotEmpty(Object obj) {
+        return !isEmpty(obj);
+    }
+
+    public static boolean isEmpty(Object obj) {
+        if (obj == null) {
+            return true;
+        }
+        if ((obj instanceof CharSequence)) {
+            return ((CharSequence) obj).length() == 0;
+        }
+        if ((obj instanceof Collection)) {
+            return CollectionUtils.isEmpty((Collection) obj);
+        }
+        if ((obj instanceof Map)) {
+            return (((Map) obj).isEmpty());
+        }
+        if (obj.getClass().isArray()) {
+            return Array.getLength(obj) == 0;
+        }
+        /*
+         * 和上面的判断数组效果一样,但是不会报空指针 if (obj instanceof Object[]) { return
+         * (((Object[])obj).length == 0); }
+         */
+        return false;
+    }
+
+    /**
+     * 只有true 或者 "true" 返回 true,否则返回false
+     *
+     * @param obj
+     * @return true or false
+     */
+    public static boolean getBoolean(Object obj) {
+        return isEmpty(obj) ? false : Boolean.valueOf(obj.toString());
+    }
+
 }

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

@@ -3,4 +3,47 @@ server:
 spring:
   # 应用名称
   application:
-    name: zkt-project-alarm
+    name: zkt-project-alarm
+  datasource:
+    url: jdbc:mysql://39.102.43.179:9934/alarm-quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false
+    driver-class-name: com.mysql.jdbc.Driver  # mysql8.0以前使用com.mysql.jdbc.Driver
+    username: root
+    password: brzj123456
+    druid:
+      initial-size: 1  # 初始化大小
+      min-idle: 1  # 最小
+      max-active: 3  # 最大
+      max-wait: 60000  # 配置获取连接等待超时的时间
+      time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      min-evictable-idle-time-millis: 300000  # 指定一个空闲连接最少空闲多久后可被清除,单位是毫秒
+      validationQuery: select 'x'
+      test-while-idle: true  # 当连接空闲时,是否执行连接测试
+      test-on-borrow: false  # 当从连接池借用连接时,是否测试该连接
+      test-on-return: false  # 在连接归还到连接池时是否测试该连接
+      filters: config,wall,stat  # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+      poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小
+      MaxPoolPreparedStatementPerConnectionSize: 20
+      maxOpenPreparedStatements: 50
+      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+      connectionProperties: druid.stat.slowSqlMillis=500;druid.stat.logSlowSql=true;config.decrypt=false
+  quartz:
+    job-store-type: jdbc
+    jdbc:
+      initialize-schema: never  # always never
+      comment-prefix: '#'
+group:
+  code: wd   #标识哪个接团 比如万达使用wd
+  alarm:
+    host: 127.0.0.1     #netty IP
+    port: 8082          #netty 端口
+terminal:  #边缘端IOT采集程序地址
+  alarm:  # 拼接后的地址为ws://host:port/suffix
+    compress: false    #采用的是压缩方式还是不压缩方式  true-压缩 false-不压缩
+    host: 39.96.55.242
+    port: 9981
+    suffix: websocket   #websocker后缀
+project:
+  id: 1101050029  #项目ID
+system:
+  id: system  #默认用户表示 ,用于报警记录的创建人
+

+ 3 - 3
src/main/resources/logback-spring.xml

@@ -5,9 +5,9 @@ debug:  当此属性设置为true时,将打印出logback内部日志信息,
 configuration 子节点为 appender、logger、root       -->
 <configuration scan="true" scanPeriod="60 seconds" debug="false">
     <!--用于区分不同应用程序的记录-->
-    <contextName>zk-project-control</contextName>
+    <contextName>zk-project-alarm</contextName>
     <!--日志文件所在目录,如果是tomcat,如下写法日志文件会在则为${TOMCAT_HOME}/bin/logs/目录下-->
-    <property name="LOG_HOME" value="../logs/zk-project-control"/>
+    <property name="LOG_HOME" value="../logs/zk-project-alarm"/>
     <!--控制台-->
     <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
         <encoder>
@@ -60,7 +60,7 @@ configuration 子节点为 appender、logger、root       -->
     <!--这里如果是info,spring、mybatis等框架则不会输出:TRACE < DEBUG < INFO <  WARN < ERROR-->
     <!--root是所有logger的祖先,均继承root,如果某一个自定义的logger没有指定level,就会寻找
     父logger看有没有指定级别,直到找到root。-->
-    <root level="debug">
+    <root level="INFO">
         <appender-ref ref="stdout"/>
         <appender-ref ref="infoFile"/>
         <appender-ref ref="errorFile"/>

+ 44 - 0
src/main/resources/quartz.properties

@@ -0,0 +1,44 @@
+# 固定前缀org.quartz
+# 主要分为scheduler、threadPool、jobStore、plugin等部分
+org.quartz.scheduler.instanceName=quartzScheduler
+org.quartz.scheduler.instanceId=AUTO
+# 如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true。
+# 在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做,'export '选项将被忽略
+org.quartz.scheduler.rmi.export = false
+# 如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口
+org.quartz.scheduler.rmi.proxy = false
+# 事务包裹
+org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
+# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
+org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
+
+# threadCount和threadPriority将以setter的形式注入ThreadPool实例
+# 并发个数
+org.quartz.threadPool.threadCount = 10
+org.quartz.threadPool.threadPriority = 5
+org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
+
+## 默认存储在内存中
+#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
+#持久化
+org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
+org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+
+org.quartz.jobStore.tablePrefix = ZKT_ALARM_
+org.quartz.jobStore.isClustered = true
+#设置此实例“检入”*与群集的其他实例的频率(以毫秒为单位)。影响检测失败实例的速度。
+org.quartz.jobStore.clusterCheckinInterval=10000
+#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
+# 因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
+org.quartz.jobStore.useProperties=true
+#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
+org.quartz.jobStore.misfireThreshold=600000
+#
+##默认C3P0方式配置数据库,connectionProvider方式可以指定,但是高级属性配置很不方便,不推荐
+#org.quartz.jobStore.dataSource = qzDS
+#org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
+#org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/plan?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false
+#org.quartz.dataSource.qzDS.user = root
+#org.quartz.dataSource.qzDS.password = 123456
+#org.quartz.dataSource.qzDS.maxConnections = 100
+#org.quartz.dataSource.qzDS.connectionProvider.class = com.persagy.plan.config.DruidConnectionProvider

+ 12 - 0
src/main/resources/statements.av

@@ -0,0 +1,12 @@
+
+
+## let a = 1;
+## let b = 2;s = 1;
+## c = a + b
+let m = "";
+for x in $list {
+  ## println(x);
+  m = m + x;
+}
+return m;
+## $a-($b-$c) > 100

+ 179 - 0
src/main/resources/zkt_alarm_tables_mysql_innodb.sql

@@ -0,0 +1,179 @@
+#
+# In your Quartz properties file, you'll need to set
+# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+#
+#
+# By: Ron Cordell - roncordell
+#  I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.
+
+DROP TABLE IF EXISTS ZKT_ALARM_FIRED_TRIGGERS;
+DROP TABLE IF EXISTS ZKT_ALARM_PAUSED_TRIGGER_GRPS;
+DROP TABLE IF EXISTS ZKT_ALARM_SCHEDULER_STATE;
+DROP TABLE IF EXISTS ZKT_ALARM_LOCKS;
+DROP TABLE IF EXISTS ZKT_ALARM_SIMPLE_TRIGGERS;
+DROP TABLE IF EXISTS ZKT_ALARM_SIMPROP_TRIGGERS;
+DROP TABLE IF EXISTS ZKT_ALARM_CRON_TRIGGERS;
+DROP TABLE IF EXISTS ZKT_ALARM_BLOB_TRIGGERS;
+DROP TABLE IF EXISTS ZKT_ALARM_TRIGGERS;
+DROP TABLE IF EXISTS ZKT_ALARM_JOB_DETAILS;
+DROP TABLE IF EXISTS ZKT_ALARM_CALENDARS;
+
+CREATE TABLE ZKT_ALARM_JOB_DETAILS(
+SCHED_NAME VARCHAR(120) NOT NULL,
+JOB_NAME VARCHAR(190) NOT NULL,
+JOB_GROUP VARCHAR(190) NOT NULL,
+DESCRIPTION VARCHAR(250) NULL,
+JOB_CLASS_NAME VARCHAR(250) NOT NULL,
+IS_DURABLE VARCHAR(1) NOT NULL,
+IS_NONCONCURRENT VARCHAR(1) NOT NULL,
+IS_UPDATE_DATA VARCHAR(1) NOT NULL,
+REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
+JOB_DATA BLOB NULL,
+PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_TRIGGERS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+TRIGGER_NAME VARCHAR(190) NOT NULL,
+TRIGGER_GROUP VARCHAR(190) NOT NULL,
+JOB_NAME VARCHAR(190) NOT NULL,
+JOB_GROUP VARCHAR(190) NOT NULL,
+DESCRIPTION VARCHAR(250) NULL,
+NEXT_FIRE_TIME BIGINT(13) NULL,
+PREV_FIRE_TIME BIGINT(13) NULL,
+PRIORITY INTEGER NULL,
+TRIGGER_STATE VARCHAR(16) NOT NULL,
+TRIGGER_TYPE VARCHAR(8) NOT NULL,
+START_TIME BIGINT(13) NOT NULL,
+END_TIME BIGINT(13) NULL,
+CALENDAR_NAME VARCHAR(190) NULL,
+MISFIRE_INSTR SMALLINT(2) NULL,
+JOB_DATA BLOB NULL,
+PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
+REFERENCES ZKT_ALARM_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_SIMPLE_TRIGGERS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+TRIGGER_NAME VARCHAR(190) NOT NULL,
+TRIGGER_GROUP VARCHAR(190) NOT NULL,
+REPEAT_COUNT BIGINT(7) NOT NULL,
+REPEAT_INTERVAL BIGINT(12) NOT NULL,
+TIMES_TRIGGERED BIGINT(10) NOT NULL,
+PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+REFERENCES ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_CRON_TRIGGERS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+TRIGGER_NAME VARCHAR(190) NOT NULL,
+TRIGGER_GROUP VARCHAR(190) NOT NULL,
+CRON_EXPRESSION VARCHAR(120) NOT NULL,
+TIME_ZONE_ID VARCHAR(80),
+PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+REFERENCES ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_SIMPROP_TRIGGERS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    TRIGGER_NAME VARCHAR(190) NOT NULL,
+    TRIGGER_GROUP VARCHAR(190) NOT NULL,
+    STR_PROP_1 VARCHAR(512) NULL,
+    STR_PROP_2 VARCHAR(512) NULL,
+    STR_PROP_3 VARCHAR(512) NULL,
+    INT_PROP_1 INT NULL,
+    INT_PROP_2 INT NULL,
+    LONG_PROP_1 BIGINT NULL,
+    LONG_PROP_2 BIGINT NULL,
+    DEC_PROP_1 NUMERIC(13,4) NULL,
+    DEC_PROP_2 NUMERIC(13,4) NULL,
+    BOOL_PROP_1 VARCHAR(1) NULL,
+    BOOL_PROP_2 VARCHAR(1) NULL,
+    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+    REFERENCES ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_BLOB_TRIGGERS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+TRIGGER_NAME VARCHAR(190) NOT NULL,
+TRIGGER_GROUP VARCHAR(190) NOT NULL,
+BLOB_DATA BLOB NULL,
+PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
+FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+REFERENCES ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_CALENDARS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+CALENDAR_NAME VARCHAR(190) NOT NULL,
+CALENDAR BLOB NOT NULL,
+PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_PAUSED_TRIGGER_GRPS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+TRIGGER_GROUP VARCHAR(190) NOT NULL,
+PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_FIRED_TRIGGERS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+ENTRY_ID VARCHAR(95) NOT NULL,
+TRIGGER_NAME VARCHAR(190) NOT NULL,
+TRIGGER_GROUP VARCHAR(190) NOT NULL,
+INSTANCE_NAME VARCHAR(190) NOT NULL,
+FIRED_TIME BIGINT(13) NOT NULL,
+SCHED_TIME BIGINT(13) NOT NULL,
+PRIORITY INTEGER NOT NULL,
+STATE VARCHAR(16) NOT NULL,
+JOB_NAME VARCHAR(190) NULL,
+JOB_GROUP VARCHAR(190) NULL,
+IS_NONCONCURRENT VARCHAR(1) NULL,
+REQUESTS_RECOVERY VARCHAR(1) NULL,
+PRIMARY KEY (SCHED_NAME,ENTRY_ID))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_SCHEDULER_STATE (
+SCHED_NAME VARCHAR(120) NOT NULL,
+INSTANCE_NAME VARCHAR(190) NOT NULL,
+LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
+CHECKIN_INTERVAL BIGINT(13) NOT NULL,
+PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
+ENGINE=InnoDB;
+
+CREATE TABLE ZKT_ALARM_LOCKS (
+SCHED_NAME VARCHAR(120) NOT NULL,
+LOCK_NAME VARCHAR(40) NOT NULL,
+PRIMARY KEY (SCHED_NAME,LOCK_NAME))
+ENGINE=InnoDB;
+
+CREATE INDEX IDX_ZKT_ALARM_J_REQ_RECOVERY ON ZKT_ALARM_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
+CREATE INDEX IDX_ZKT_ALARM_J_GRP ON ZKT_ALARM_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
+
+CREATE INDEX IDX_ZKT_ALARM_T_J ON ZKT_ALARM_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
+CREATE INDEX IDX_ZKT_ALARM_T_JG ON ZKT_ALARM_TRIGGERS(SCHED_NAME,JOB_GROUP);
+CREATE INDEX IDX_ZKT_ALARM_T_C ON ZKT_ALARM_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
+CREATE INDEX IDX_ZKT_ALARM_T_G ON ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
+CREATE INDEX IDX_ZKT_ALARM_T_STATE ON ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
+CREATE INDEX IDX_ZKT_ALARM_T_N_STATE ON ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
+CREATE INDEX IDX_ZKT_ALARM_T_N_G_STATE ON ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
+CREATE INDEX IDX_ZKT_ALARM_T_NEXT_FIRE_TIME ON ZKT_ALARM_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
+CREATE INDEX IDX_ZKT_ALARM_T_NFT_ST ON ZKT_ALARM_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
+CREATE INDEX IDX_ZKT_ALARM_T_NFT_MISFIRE ON ZKT_ALARM_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
+CREATE INDEX IDX_ZKT_ALARM_T_NFT_ST_MISFIRE ON ZKT_ALARM_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
+CREATE INDEX IDX_ZKT_ALARM_T_NFT_ST_MISFIRE_GRP ON ZKT_ALARM_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
+
+CREATE INDEX IDX_ZKT_ALARM_FT_TRIG_INST_NAME ON ZKT_ALARM_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
+CREATE INDEX IDX_ZKT_ALARM_FT_INST_JOB_REQ_RCVRY ON ZKT_ALARM_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
+CREATE INDEX IDX_ZKT_ALARM_FT_J_G ON ZKT_ALARM_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
+CREATE INDEX IDX_ZKT_ALARM_FT_JG ON ZKT_ALARM_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
+CREATE INDEX IDX_ZKT_ALARM_FT_T_G ON ZKT_ALARM_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
+CREATE INDEX IDX_ZKT_ALARM_FT_TG ON ZKT_ALARM_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
+
+commit;

+ 64 - 0
src/test/java/com/persagy/zktprojectalarm/Mytest.java

@@ -0,0 +1,64 @@
+package com.persagy.zktprojectalarm;
+
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.base.CaseFormat;
+import com.persagy.ZktProjectAlarmApplication;
+import com.persagy.cache.AlarmInfoCache;
+import com.persagy.entity.AlarmDefine;
+import com.persagy.service.AlarmDefineService;
+import com.persagy.utils.LockUtil;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+
+
+@SpringBootTest(classes = ZktProjectAlarmApplication.class)
+public class Mytest {
+    @Autowired
+    LockUtil lockUtil;
+    @Autowired
+    AlarmDefineService alarmDefineService;
+    @Autowired
+    AlarmInfoCache alarmInfoCache;
+    @Test
+    public void test1 () throws Exception {
+        lockUtil.await();
+        new CountDownLatch(1).await();
+    }
+
+
+    @Test
+    public void test2 () throws Exception {
+        alarmDefineService.listAlarmDefine();
+        ConcurrentHashMap<String, AlarmDefine> alarmDefineMap = alarmInfoCache.getAlarmDefineMap();
+        for (Map.Entry entry :alarmDefineMap.entrySet()) {
+            System.out.println("[key]:"+entry.getKey()+"[value]"+entry.getValue());
+        }
+        System.out.println("--------------------");
+        ConcurrentHashMap<String, List<AlarmDefine>> infoAlarmMap = alarmInfoCache.getInfoAlarmMap();
+        for (Map.Entry entry :infoAlarmMap.entrySet()) {
+            System.out.println("[key]:"+entry.getKey()+"[value]"+ JSONObject.toJSONString(entry.getValue()));
+        }
+
+        new CountDownLatch(1).await();
+    }
+
+
+    @Test
+    public void test3 () throws Exception {
+        List<String> list= Arrays.asList("WE","We","W_e","asNs","ss_Sd");
+        for (String column : list) {
+            System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,column));
+        }
+        new CountDownLatch(1).await();
+    }
+
+}

+ 10 - 1
src/test/java/com/persagy/zktprojectalarm/ZktProjectAlarmApplicationTests.java

@@ -1,13 +1,22 @@
 package com.persagy.zktprojectalarm;
 
+import com.persagy.client.WebSocketClientFactory;
 import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import java.util.concurrent.CountDownLatch;
+
 @SpringBootTest
 class ZktProjectAlarmApplicationTests {
+    @Autowired
+    private WebSocketClientFactory webSocketClientFactory;
 
     @Test
-    void contextLoads() {
+    void contextLoads() throws InterruptedException {
+        System.out.println("启动...");
+        webSocketClientFactory.retryOutCallWebSocketClient();
+        new CountDownLatch(1).await();
     }
 
 }