Procházet zdrojové kódy

转发服务代码整合

hecheng před 3 roky
rodič
revize
35f03157e7
40 změnil soubory, kde provedl 3637 přidání a 4 odebrání
  1. 19 0
      zd-modules/zd-algorithm/pom.xml
  2. 4 1
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/AlgorithmApplication.java
  3. 105 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/component/AppStartedListener.java
  4. 293 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/config/AlgorithmYml.java
  5. 110 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/config/WebConfig.java
  6. 45 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/controller/AlarmPhotoController.java
  7. 167 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/controller/SignInCheckController.java
  8. 13 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/AnalysisData.java
  9. 30 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/BaseRequestData.java
  10. 14 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/DataPostAnalysisRespDto.java
  11. 62 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/ImageRequestData.java
  12. 18 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/ImgPostResponse.java
  13. 103 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/RequestData.java
  14. 96 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/VideoRequestData.java
  15. 49 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/listener/StartListener.java
  16. 51 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/properties/FireProperties.java
  17. 17 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/CheckResultValid.java
  18. 544 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/CheckService.java
  19. 126 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/FireImageService.java
  20. 31 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/ImageCheckResultValidImpl.java
  21. 78 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/ImageService.java
  22. 103 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/LoginService.java
  23. 58 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/PeopleCheckResultValidImpl.java
  24. 263 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/SendSginAccessLogService.java
  25. 18 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/ShiYanFuCheckResultValidImpl.java
  26. 17 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/VideoCheckResultValid.java
  27. 31 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/VideoCheckResultValidImpl.java
  28. 46 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/mqtt/CommonSend.java
  29. 28 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/mqtt/MqttOutListener.java
  30. 39 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/mqtt/MqttProducer.java
  31. 78 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/thread/ThreadPoolTaskConfig.java
  32. 93 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/util/Base64DecodedMultipartFile.java
  33. 76 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/util/Base64ToMultipartFile.java
  34. 201 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/util/FileUploadUtils.java
  35. 93 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/Base64DecodedMultipartFile.java
  36. 76 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/Base64ToMultipartFile.java
  37. 201 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/FileUploadUtils.java
  38. 113 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/HttpUtils.java
  39. 51 0
      zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/VideoUtils.java
  40. 77 3
      zd-modules/zd-algorithm/src/main/resources/application.yml

+ 19 - 0
zd-modules/zd-algorithm/pom.xml

@@ -97,5 +97,24 @@
             <scope>system</scope>
             <systemPath>${pom.basedir}/src/main/resources/libs/arcsoft-sdk-face-3.0.0.0.jar</systemPath>
         </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.bytedeco/ffmpeg-platform -->
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>ffmpeg-platform</artifactId>
+            <version>5.0-1.5.7</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.bytedeco/javacv -->
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>javacv</artifactId>
+            <version>1.5.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-mock</artifactId>
+            <version>2.0.8</version>
+        </dependency>
     </dependencies>
 </project>

+ 4 - 1
zd-modules/zd-algorithm/src/main/java/com/zd/alg/AlgorithmApplication.java

@@ -1,10 +1,13 @@
 package com.zd.alg;
 
 
+import com.zd.alg.forward.config.AlgorithmYml;
+import com.zd.alg.forward.properties.FireProperties;
 import com.zd.common.security.annotation.EnableCustomConfig;
 import com.zd.common.security.annotation.EnableRyFeignClients;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import zd.common.mqtt.config.MqttProperties;
@@ -12,7 +15,7 @@ import zd.common.mqtt.config.MqttProperties;
 @EnableCustomConfig
 @EnableRyFeignClients
 @SpringBootApplication
-@EnableConfigurationProperties({MqttProperties.class})
+@EnableConfigurationProperties({MqttProperties.class, AlgorithmYml.class, FireProperties.class})
 @Slf4j
 public class AlgorithmApplication {
 

+ 105 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/component/AppStartedListener.java

@@ -0,0 +1,105 @@
+package com.zd.alg.forward.component;
+
+import com.zd.common.core.utils.Assert;
+import com.zd.alg.forward.config.AlgorithmYml;
+import com.zd.alg.forward.serivce.CheckResultValid;
+import com.zd.alg.forward.serivce.VideoCheckResultValid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+@Component
+public class AppStartedListener implements ApplicationListener<ApplicationStartedEvent> {
+    Logger logger = LoggerFactory.getLogger(AppStartedListener.class);
+    @Autowired
+    AlgorithmYml algorithmYml;
+
+
+    @Override
+    public void onApplicationEvent(@NonNull ApplicationStartedEvent applicationStartedEvent) {
+        //日志地址验证
+        URL url;
+        InputStream in = null;
+        try {
+            url = new URL(algorithmYml.getLoginUri() + algorithmYml.getCheckLogUrl());
+            //联通性测试
+            in = url.openStream();
+            logger.info("检查日志地址连通性测试通过!");
+        } catch (MalformedURLException e) {
+            logger.error("关闭项目!!!!!!检查日志地址配置错误url:{}", algorithmYml.getCheckLogUrl());
+            //关闭服务
+            applicationStartedEvent.getApplicationContext().close();
+        } catch (IOException e) {
+            e.printStackTrace();
+            logger.error("检查日志地址连通性测试失败!url:{},请检查实验室项目是否正常,或url配置是否正确!", algorithmYml.getCheckLogUrl());
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        //算法解析类配置
+        logger.info("开始注入算法结果解析");
+        //注入
+        algorithmYml.getAlgorithmMap().forEach((key, value) -> {
+            try {
+                Class<?> classPath = value.getClassPath();
+                boolean assignableFrom = CheckResultValid.class.isAssignableFrom(value.getClassPath());
+                Assert.isTrue(assignableFrom, "算法解析实现类错误!必须是CheckResultValid的实现类");
+                boolean b = applicationStartedEvent.getApplicationContext().getBeanFactory().containsBean(classPath.getName());
+                logger.info("bean:{},是否存在:{}", classPath.getName(), b);
+                if (b) {
+                    Object bean = applicationStartedEvent.getApplicationContext().getBeanFactory().getBean(classPath.getName());
+                    value.setCheckResultValid((CheckResultValid) bean);
+                    return;
+                }
+                Object o = value.getClassPath().newInstance();
+                //注入IOC容器
+                applicationStartedEvent.getApplicationContext().getBeanFactory().registerSingleton(classPath.getName(), o);
+                //设置
+                value.setCheckResultValid((CheckResultValid) o);
+            } catch (Exception e) {
+                logger.error("算法结果解析注入失败!");
+                e.printStackTrace();
+                logger.error("关闭项目!!!!请检查算法结果解析配置!!");
+                //关闭服务
+                applicationStartedEvent.getApplicationContext().close();
+            }
+
+
+        });
+        algorithmYml.getVideoAidMap().forEach((key, value) -> {
+            try {
+                String name = value.getName();
+
+                boolean b = applicationStartedEvent.getApplicationContext().getBeanFactory().containsBean(name);
+                logger.info("bean:{},是否存在:{}", name, b);
+                if (!b) {
+                    logger.error("bean:{},未被注入", name);
+                }
+                Object bean = applicationStartedEvent.getApplicationContext().getBeanFactory().getBean(name);
+                value.setVideoCheckResultValid((VideoCheckResultValid) bean);
+
+            } catch (Exception e) {
+                logger.error("视频算法结果解析注入失败!");
+                e.printStackTrace();
+                logger.error("关闭项目!!!!请检查算法结果解析配置!!");
+                //关闭服务
+                applicationStartedEvent.getApplicationContext().close();
+            }
+        });
+    }
+}

+ 293 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/config/AlgorithmYml.java

@@ -0,0 +1,293 @@
+package com.zd.alg.forward.config;
+
+
+import com.zd.alg.forward.serivce.CheckResultValid;
+import com.zd.alg.forward.serivce.ImageCheckResultValidImpl;
+import com.zd.alg.forward.serivce.VideoCheckResultValid;
+import com.zd.common.core.exception.ServiceException;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+@ConfigurationProperties("sys.config")
+@RefreshScope
+@Validated
+public class AlgorithmYml {
+
+    /**
+     * 视频算法
+     */
+    Map<Integer,VideoCheckValid> videoAidMap = Collections.emptyMap();
+
+    /**
+     * 算法集合
+     */
+    Map<Integer,CheckValid> algorithmMap = Collections.emptyMap();
+
+    /**
+     * 警报配置
+     */
+    Map<Integer,AlarmConfig> alarmConfigMap = Collections.emptyMap();
+
+    /**
+     * 中转服务图片提交地址
+     */
+    @NotBlank(message = "中转服务图片提交地址")
+    String imgPostUrl;
+
+
+    String imgTemp;
+
+    /**
+     * save log  Url
+     */
+    @NotBlank(message = "检查日志提交url地址不能为空")
+    String checkLogUrl;
+
+    /**
+    * 跳过次数 -1 为不开启跳过 默认为3
+     */
+    Integer jumpThreshold=2;
+
+
+    @NotBlank(message = "项目地址不能为空")
+    String loginUri;
+
+    /**
+     * 转发目标地址
+     */
+    String targetUrl;
+
+    /**
+     * 1号喇叭IP
+     */
+    String loudspeakerIp1;
+    /**
+     * 2号喇叭IP
+     */
+    String loudspeakerIp2;
+
+    /**
+     * 喇叭开关
+     */
+    boolean loudspeakerSwitch=false;
+
+    public boolean isLoudspeakerSwitch() {
+        return loudspeakerSwitch;
+    }
+
+    public void setLoudspeakerSwitch(boolean loudspeakerSwitch) {
+        this.loudspeakerSwitch = loudspeakerSwitch;
+    }
+
+    public String getLoudspeakerIp1() {
+        return loudspeakerIp1;
+    }
+
+    public void setLoudspeakerIp1(String loudspeakerIp1) {
+        this.loudspeakerIp1 = loudspeakerIp1;
+    }
+
+    public String getLoudspeakerIp2() {
+        return loudspeakerIp2;
+    }
+
+    public void setLoudspeakerIp2(String loudspeakerIp2) {
+        this.loudspeakerIp2 = loudspeakerIp2;
+    }
+
+    public String getImgTemp() {
+        return imgTemp;
+    }
+
+    public void setImgTemp(String imgTemp) {
+        this.imgTemp = imgTemp;
+    }
+
+    public Integer getJumpThreshold() {
+        return jumpThreshold;
+    }
+
+    public void setJumpThreshold(Integer jumpThreshold) {
+        this.jumpThreshold = jumpThreshold;
+    }
+
+    public Map<Integer, VideoCheckValid> getVideoAidMap() {
+        return Collections.unmodifiableMap(videoAidMap);
+    }
+
+    public void setVideoAidMap(Map<Integer, VideoCheckValid> videoAidMap) {
+        this.videoAidMap = videoAidMap;
+    }
+
+    public Map<Integer, AlarmConfig> getAlarmConfigMap() {
+        return alarmConfigMap;
+    }
+
+    public void setAlarmConfigMap(Map<Integer, AlarmConfig> alarmConfigMap) {
+        this.alarmConfigMap = alarmConfigMap;
+    }
+
+    public String getLoginUri() {
+        return loginUri;
+    }
+
+    public void setLoginUri(String loginUri) {
+        this.loginUri = loginUri;
+    }
+
+    public String getTargetUrl() {
+        return targetUrl;
+    }
+
+    public void setTargetUrl(String targetUrl) {
+        this.targetUrl = targetUrl;
+    }
+
+    public String getCheckLogUrl() {
+        return checkLogUrl;
+    }
+
+    public void setCheckLogUrl(String checkLogUrl) {
+        this.checkLogUrl = checkLogUrl;
+    }
+
+    public static class VideoCheckValid{
+        @NotNull (message = "描述不能为空")
+        String tips;
+
+        @NotBlank (message = "算法结构解析实现类不能为空")
+        private String name = "videoCheckResultValidImpl";
+
+        private VideoCheckResultValid videoCheckResultValid;
+
+        public String getTips() {
+            return tips;
+        }
+
+        public void setTips(String tips) {
+            this.tips = tips;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public VideoCheckResultValid getVideoCheckResultValid() {
+            return videoCheckResultValid;
+        }
+
+        public void setVideoCheckResultValid(VideoCheckResultValid videoCheckResultValid) {
+            this.videoCheckResultValid = videoCheckResultValid;
+        }
+    }
+
+    public static class AlarmConfig{
+        private String funNum;
+        private String describe;
+
+        public String getFunNum() {
+            return funNum;
+        }
+
+        public void setFunNum(String funNum) {
+            this.funNum = funNum;
+        }
+
+        public String getDescribe() {
+            return describe;
+        }
+
+        public void setDescribe(String describe) {
+            this.describe = describe;
+        }
+    }
+
+    public static class CheckValid {
+        //算法ID
+        @NotNull (message = "算法ID不能为空")
+        private Integer algoId;
+        //cid
+        @NotNull (message = "数据ID不能为空")
+        private String did;
+
+        private String algorithmName;
+
+        //默认使用图片post 推送结果解析类
+        @NotNull (message = "算法结构解析实现类不能为空")
+        private Class<?> classPath = ImageCheckResultValidImpl.class;
+        //检查结果检查实现类
+        private CheckResultValid checkResultValid;
+
+        public String getAlgorithmName() {
+            return algorithmName;
+        }
+
+        public void setAlgorithmName(String algorithmName) {
+            this.algorithmName = algorithmName;
+        }
+
+        public Integer getAlgoId() {
+            return algoId;
+        }
+
+        public void setAlgoId(Integer algoId) {
+            this.algoId = algoId;
+        }
+
+        public String getDid() {
+            return did;
+        }
+
+        public void setDid(String did) {
+            this.did = did;
+        }
+
+        public CheckResultValid getCheckResultValid() {
+            return checkResultValid;
+        }
+
+        public void setCheckResultValid(CheckResultValid checkResultValid) {
+            this.checkResultValid = checkResultValid;
+        }
+
+        public Class<?> getClassPath() {
+            return classPath;
+        }
+
+        public void setClassPath(Class<?> classPath) {
+            this.classPath = classPath;
+        }
+    }
+
+    public String getImgPostUrl() {
+        return imgPostUrl;
+    }
+
+    public void setImgPostUrl(String imgPostUrl) {
+        this.imgPostUrl = imgPostUrl;
+    }
+
+    public Map<Integer, CheckValid> getAlgorithmMap() {
+        return Collections.unmodifiableMap(algorithmMap);
+    }
+
+    public void setAlgorithmMap(Map<Integer,CheckValid> algorithmMap) {
+        this.algorithmMap = algorithmMap;
+    }
+
+    public CheckValid getCheckValid(Integer code) {
+        return Optional.ofNullable(getAlgorithmMap().get(code))
+                .orElseThrow(() -> new ServiceException("未配置对应算法code:" + code));
+    }
+}

+ 110 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/config/WebConfig.java

@@ -0,0 +1,110 @@
+package com.zd.alg.forward.config;
+
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.ConnectionKeepAliveStrategy;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Administrator
+ */
+@Configuration
+public class WebConfig {
+
+    @Resource
+    private RestTemplateBuilder restTemplateBuilder;
+    /**
+     * 自创建RestTemplate
+     * 这里需要重命名, 因为会导致原来的负载均衡冲突失效 导致项目起不来
+     * @return restTemplateLocal
+     */
+    @Bean
+    public static RestTemplate restTemplateLocal() {
+        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+        // 设置连接超时,单位毫秒
+        requestFactory.setConnectTimeout(10000);
+        //设置读取超时
+        requestFactory.setReadTimeout(10000);
+        RestTemplate restTemplate = new RestTemplate();
+        restTemplate.setRequestFactory(requestFactory);
+        return restTemplate;
+    }
+
+    /**
+     * 让spring管理RestTemplate,参数相关配置
+     *
+     * @return restTemplate
+     */
+    @Bean
+    public RestTemplate restTemplate() {
+        RestTemplate restTemplate = restTemplateBuilder.build();
+        restTemplate.setRequestFactory(clientHttpRequestFactory());
+        return restTemplate;
+    }
+
+    /**
+     * 客户端请求链接策略
+     *
+     * @return clientHttpRequestFactory
+     */
+    private ClientHttpRequestFactory clientHttpRequestFactory() {
+        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
+        clientHttpRequestFactory.setHttpClient(httpClientBuilder().build());
+        // 连接超时时间/毫秒
+        clientHttpRequestFactory.setConnectTimeout(60000);
+        // 读写超时时间/毫秒
+        clientHttpRequestFactory.setReadTimeout(60000);
+        // 请求超时时间/毫秒
+        clientHttpRequestFactory.setConnectionRequestTimeout(50000);
+        return clientHttpRequestFactory;
+    }
+
+    /**
+     * 设置HTTP连接管理器,连接池相关配置管理
+     *
+     * @return 客户端链接管理器
+     */
+    private HttpClientBuilder httpClientBuilder() {
+        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
+        httpClientBuilder.setConnectionManager(poolingConnectionManager());
+        ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (httpResponse, httpContext) -> 20 * 1000;
+        httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);
+        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler());
+        RequestConfig requestConfig = RequestConfig.custom()
+                .setConnectTimeout(10 * 1000)
+                .setSocketTimeout(10 * 1000)
+                .setConnectionRequestTimeout(10 * 1000)
+                .build();
+        httpClientBuilder.setDefaultRequestConfig(requestConfig);
+        return httpClientBuilder;
+    }
+
+    /**
+     * 链接线程池管理,可以keep-alive不断开链接请求,这样速度会更快 MaxTotal 连接池最大连接数 DefaultMaxPerRoute
+     * 每个主机的并发 ValidateAfterInactivity
+     * 可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过,释放socket重新建立
+     *
+     * @return poolingConnectionManager
+     */
+    private HttpClientConnectionManager poolingConnectionManager() {
+        PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
+        poolingConnectionManager.setMaxTotal(1000);
+        poolingConnectionManager.setDefaultMaxPerRoute(5000);
+        poolingConnectionManager.setValidateAfterInactivity(30000);
+        poolingConnectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
+        poolingConnectionManager.closeExpiredConnections();
+        return poolingConnectionManager;
+    }
+}

+ 45 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/controller/AlarmPhotoController.java

@@ -0,0 +1,45 @@
+package com.zd.alg.forward.controller;
+
+
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import com.zd.common.core.domain.R;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.common.swagger.config.Knife4jConfiguration;
+import com.zd.alg.forward.serivce.ImageService;
+import com.zd.system.api.domain.SysFile;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+
+/**
+ * 报警拍照服务
+ */
+@Api(tags = {"报警拍照服务"})
+@ApiSupport(author = Knife4jConfiguration.Author.ZP)
+@RestController
+@RequestMapping("/alarm")
+public class AlarmPhotoController {
+
+    @Resource
+    private ImageService imageService;
+
+    @ApiOperation(value = "拍照")
+    @GetMapping("/photograph")
+    public R<SysFile> photograph(@RequestParam("streamUrl") String streamUrl) {
+        try {
+            SysFile sysFile = imageService.photograph(streamUrl);
+            if (sysFile!=null){
+                return R.ok(sysFile);
+            }
+            return R.fail("图片抓拍失败");
+        } catch (IOException|ServiceException e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+}

+ 167 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/controller/SignInCheckController.java

@@ -0,0 +1,167 @@
+package com.zd.alg.forward.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import com.zd.alg.forward.config.AlgorithmYml;
+import com.zd.alg.forward.domain.VideoRequestData;
+import com.zd.alg.forward.serivce.CheckService;
+import com.zd.alg.forward.serivce.FireImageService;
+import com.zd.alg.forward.serivce.SendSginAccessLogService;
+import com.zd.alg.forward.serivce.VideoCheckResultValidImpl;
+import com.zd.common.core.domain.R;
+import com.zd.common.core.domain.Result;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.common.core.utils.Assert;
+import com.zd.common.redis.service.RedisService;
+import com.zd.common.swagger.config.Knife4jConfiguration;
+import com.zd.system.api.domain.Algorithm;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.util.Map;
+import java.util.Optional;
+
+
+/**
+ * 签到验证
+ */
+@Api(tags = {"签到验证:算法转发服务"})
+@ApiSupport(author = Knife4jConfiguration.Author.ZP)
+@RestController
+@RequestMapping("/signIn/check")
+public class SignInCheckController {
+
+    Logger logger = LoggerFactory.getLogger(SignInCheckController.class);
+
+    @Autowired
+    CheckService checkService;
+
+    @Autowired
+    VideoCheckResultValidImpl videoCheckResultValid;
+
+    @Autowired
+    RedisService redisService;
+
+    @Autowired
+    SendSginAccessLogService sendSginAccessLogService;
+
+    @Autowired
+    AlgorithmYml algorithmYml;
+
+    @Resource
+    private FireImageService fireImageService;
+
+    /**
+     * 进入项验证
+     *
+     * @param id   进出记录ID
+     * @param code 字典sub_check_in类型下的的值
+     * @param file 带验证图片
+     */
+    @ApiOperation(value = "进入项验证")
+    @PostMapping("/{code}/{id}")
+    public R checkIn(@ApiParam("进出记录ID") @PathVariable("id") Long id, @ApiParam("验证类型编码") @PathVariable("code") String code, @RequestParam("file") MultipartFile file) {
+        return checkService.checkAndCommit(code, file, id);
+    }
+
+    /**
+     * 进入项验证:合并
+     *
+     * @param id   进出记录ID
+     * @param file 带验证图片
+     */
+    @ApiOperation(value = "进入项验证:合并,codes格式:1,2,3")
+    @PostMapping("/checkInAll")
+    public R checkInAll(@ApiParam("进出记录ID") @RequestParam("id") Long id,
+                        @ApiParam("图片文件") @RequestParam("file") MultipartFile file,
+                        @ApiParam("实验室ID") @RequestParam("subId") Long subId) {
+        return checkService.checkAndCommit(id, file, subId);
+    }
+
+
+    /**
+     * 进入项验证:mock方法
+     *
+     * @param code 字典sub_check_in类型下的的值
+     * @param file 带验证图片
+     */
+    @ApiOperation(value = "进入项验证:mock方法")
+    @PostMapping("/mock/{code}/{id}")
+    public R checkInMock(@ApiParam("进出记录ID") @PathVariable("id") Long id, @ApiParam("验证类型编码") @PathVariable("code") String code, @RequestParam("file") MultipartFile file) {
+        return checkService.mockTest(code, file, id);
+    }
+
+
+    @ApiOperation(value = "进入项验证(测试):必须登录权限后面加!!")
+    @PostMapping("/test1")
+    public R test1() {
+        checkService.send("1", 100L, true, "");
+        return R.ok();
+    }
+
+
+    @ApiOperation(value = "视频回调,code 来自字典:检查项")
+    @PostMapping("/callback")
+    public R callBack(@ApiParam("验证类型编码") @RequestBody VideoRequestData videoRequestData) {
+        Map<Integer, AlgorithmYml.VideoCheckValid> videoAidMap = algorithmYml.getVideoAidMap();
+        AlgorithmYml.VideoCheckValid algorithmCheck = Optional.ofNullable(videoAidMap.get(videoRequestData.getAid()))
+                .orElseThrow(() -> new ServiceException("未配置的算法类型"));
+        Assert.isTrue(videoRequestData.getStatus() == 1, "算法服务检测失败!。");
+        redisService.setCacheObject("VIDEO_CID:" + videoRequestData.getCid(), Long.valueOf(videoRequestData.getCid()));
+        Long subId = redisService.getCacheObject("VIDEO_CID:" + videoRequestData.getCid());
+        if (subId == null) {
+            logger.info("未找到CID对应的实验室");
+            return R.fail("未找到CID对应的实验室");
+        }
+        checkService.checkVideo(algorithmCheck, videoRequestData, subId);
+        return R.ok();
+    }
+
+    @ApiOperation(value = "警报回调")
+    @PostMapping("/alarmCallBack")
+    public Result fireCallBack(@RequestBody VideoRequestData videoRequestData) {
+        Integer aid = videoRequestData.getAid();
+        if (aid!=null){
+            if (aid.equals(9610)){
+                logger.info("=====================>测试回调了。。。{}",videoRequestData.getAlgo_name());
+                checkService.sendAlarm(videoRequestData);
+            }else if (aid.equals(9690)){
+                checkService.playMp3();
+            }
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "语音播报")
+    @PostMapping("/playMp3")
+    public Result playMp3() {
+        checkService.playMp3();
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "测试算法保存")
+    @PostMapping("/algorithm/test/save")
+    public R saveAlgorithmTest(@RequestBody JSONObject aa) {
+        Algorithm algorithm = new Algorithm();
+        algorithm.setAlgorithmResult(aa.toJSONString());
+        sendSginAccessLogService.saveAlgorithmResult(algorithm);
+        return R.ok(aa);
+    }
+
+    /**
+     * 火焰算法验证
+     * @param file 带验证图片
+     */
+    @ApiOperation(value = "进入项验证:合并,codes格式:1,2,3")
+    @PostMapping("/checkFire")
+    public R checkFire(@ApiParam("图片文件") @RequestParam("file") MultipartFile file) {
+        return R.ok(fireImageService.catchImage(file));
+    }
+}

+ 13 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/AnalysisData.java

@@ -0,0 +1,13 @@
+package com.zd.alg.forward.domain;
+
+import lombok.Data;
+
+@Data
+public class AnalysisData {
+
+    private int code;
+    private String msg;
+    private Object result;
+    private String ret_image;
+    private String src_image;
+}

+ 30 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/BaseRequestData.java

@@ -0,0 +1,30 @@
+package com.zd.alg.forward.domain;
+
+/**
+ * @Author: zhoupan
+ * @Date: 2021/11/13/10:58
+ * @Description:
+ */
+public class BaseRequestData {
+
+    //算法ID,由极视角定义的算法ID,只读
+    private Long aid;
+    //摄像头ID,存在两种情况:
+    private String cid;
+
+    public Long getAid() {
+        return aid;
+    }
+
+    public void setAid(Long aid) {
+        this.aid = aid;
+    }
+
+    public String getCid() {
+        return cid;
+    }
+
+    public void setCid(String cid) {
+        this.cid = cid;
+    }
+}

+ 14 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/DataPostAnalysisRespDto.java

@@ -0,0 +1,14 @@
+package com.zd.alg.forward.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class DataPostAnalysisRespDto {
+
+    private Integer aid;
+    private String did;
+    private String extension;
+    private List<AnalysisData> analysisDatas;
+}

+ 62 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/ImageRequestData.java

@@ -0,0 +1,62 @@
+package com.zd.alg.forward.domain;
+
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 图片输入源参数
+ * @Author: zhoupan
+ * @Date: 2021/11/13/10:59
+ * @Description:
+ */
+public class ImageRequestData extends BaseRequestData{
+    //分析图片:form-data
+    MultipartFile image;
+    //时间戳(单位秒)
+    Long timestamp;
+    //是否同步:1-同步(默认),0-异步
+    int sync;
+    //回调地址,设置异步时必传
+    String callback;
+    //扩展字段,响应时会返回该扩展字段
+    String extension;
+
+    public MultipartFile getImage() {
+        return image;
+    }
+
+    public void setImage(MultipartFile image) {
+        this.image = image;
+    }
+
+    public Long getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(Long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getSync() {
+        return sync;
+    }
+
+    public void setSync(int sync) {
+        this.sync = sync;
+    }
+
+    public String getCallback() {
+        return callback;
+    }
+
+    public void setCallback(String callback) {
+        this.callback = callback;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+}

+ 18 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/ImgPostResponse.java

@@ -0,0 +1,18 @@
+package com.zd.alg.forward.domain;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 图片算法返回值
+ */
+@Data
+public class ImgPostResponse<T> {
+    //1000 成功
+    private long status_code;
+
+    private String message;
+
+    private T data;
+}

+ 103 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/RequestData.java

@@ -0,0 +1,103 @@
+package com.zd.alg.forward.domain;
+
+/**
+ * @Author: zhoupan
+ * @Date: 2021/11/13/10:10
+ * @Description:
+ */
+public class RequestData {
+
+    //算法ID,由极视角定义的算法ID,只读
+    private String aid;
+    //摄像头ID,存在两种情况:
+    private String cid;
+    //摄像头对应拉流地址/rtsp
+    private Long cid_ur;
+    //算法服务器时间戳,unix标准时间戳格式
+    private Long time_stamp;
+    //状态值
+    private int status;
+    //srcpic_name 报警输出图片关联的原始分析图片
+    private String srcpic_name;
+    //原始分析图片,base64格式编码
+    private String srcpic_data;
+    /**
+     * 报警图片命名,格式为 “时间(精确到秒)_us(微秒)_cid(摄像头ID)_fix(输入或输 出).jpg”,
+     * 例:20180719121005_266236_3_out.jpg
+     */
+    private String pic_name;
+    //报警结果图片,base64格式编码 不带换行
+    private String pic_data;
+
+    public String getAid() {
+        return aid;
+    }
+
+    public void setAid(String aid) {
+        this.aid = aid;
+    }
+
+    public String getCid() {
+        return cid;
+    }
+
+    public void setCid(String cid) {
+        this.cid = cid;
+    }
+
+    public Long getCid_ur() {
+        return cid_ur;
+    }
+
+    public void setCid_ur(Long cid_ur) {
+        this.cid_ur = cid_ur;
+    }
+
+    public Long getTime_stamp() {
+        return time_stamp;
+    }
+
+    public void setTime_stamp(Long time_stamp) {
+        this.time_stamp = time_stamp;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    public String getSrcpic_name() {
+        return srcpic_name;
+    }
+
+    public void setSrcpic_name(String srcpic_name) {
+        this.srcpic_name = srcpic_name;
+    }
+
+    public String getSrcpic_data() {
+        return srcpic_data;
+    }
+
+    public void setSrcpic_data(String srcpic_data) {
+        this.srcpic_data = srcpic_data;
+    }
+
+    public String getPic_name() {
+        return pic_name;
+    }
+
+    public void setPic_name(String pic_name) {
+        this.pic_name = pic_name;
+    }
+
+    public String getPic_data() {
+        return pic_data;
+    }
+
+    public void setPic_data(String pic_data) {
+        this.pic_data = pic_data;
+    }
+}

+ 96 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/domain/VideoRequestData.java

@@ -0,0 +1,96 @@
+package com.zd.alg.forward.domain;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 视频算法回调 返回值
+ */
+@ToString
+@Data
+public class VideoRequestData {
+
+    /**
+     * 算法分析前图片数据,Base64,具体看平台是否勾选需要
+     */
+    private String srcpic_data;
+
+    /**
+     * 报警帧原始图片文件名
+     */
+    private String srcpic_name;
+
+    /**
+     * 算法分析前图片地址,具体请看示例
+     */
+    private String srcpic_url;
+
+    /**
+     * 算法分析的时间,1970‑01‑01 00:00:00至今的秒数
+     */
+    private Long time_stamp;
+
+    /**
+     * 算法分析后图片数据,Base64,具体看平台是否勾选需要
+     */
+    private String pic_data;
+
+    /**
+     * 算法分析后图片地址(非人脸算法必有),具体请看示例
+     */
+    private String pic_url;
+
+    private Map<String,Object> data;
+
+    private String did;
+    private String extension;
+    private List<AnalysisData> analysisDatas;
+
+    /**
+     * 算法id
+     */
+    private Integer aid;
+
+    /**
+     * 摄像头拉流地址,对应运行参数cid_url
+     */
+    private String cid_url;
+
+    /**
+     * 相机id
+     */
+    private String cid;
+
+    /**
+     * 报警状态,1为报警,其它为非报警
+     */
+    private int status;
+
+    /**
+     * 算法分析后的报警图片文件名
+     */
+    private String pic_name;
+
+    /**
+     * 算法名称
+     */
+    private String algo_name;
+
+    /**
+     * 告警摄像头名称
+     */
+    private String device_name;
+
+    /**
+     * 经度
+     */
+    private Double location_lng;
+    /**
+     * 纬度
+     */
+    private Double location_lat;
+
+}

+ 49 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/listener/StartListener.java

@@ -0,0 +1,49 @@
+package com.zd.alg.forward.listener;
+
+import com.zd.alg.forward.properties.FireProperties;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.alg.forward.serivce.FireImageService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 监听Spring容器启动完成,完成后启动Netty服务器
+ *
+ * @author dgs
+ **/
+@Component
+@Slf4j
+public class StartListener implements CommandLineRunner {
+    @Resource
+    private FireImageService fireImageService;
+    @Resource
+    private FireProperties fireProperties;
+
+    @Resource
+    private ScheduledExecutorService scheduledExecutorService;
+
+    @Override
+    public void run(String... args) {
+        scheduledExecutorService.scheduleWithFixedDelay(() -> {
+            try {
+                String streamUrl = fireProperties.getStreamUrl();
+                if (streamUrl == null) {
+                    log.error("=========调用产生异常:未配置流媒体地址============");
+                    return;
+                }
+                fireImageService.catchImage();
+            } catch (ServiceException | IOException e) {
+                //异常回调,防止系统因异常问题被杀死
+                log.error("=========调用产生异常:{}============", e.getMessage());
+                run();
+            }
+        },0,fireProperties.getWaitTime(), TimeUnit.SECONDS);
+    }
+
+}

+ 51 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/properties/FireProperties.java

@@ -0,0 +1,51 @@
+package com.zd.alg.forward.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Administrator
+ */
+@ConfigurationProperties("fire")
+@RefreshScope
+@Validated
+@Data
+public class FireProperties {
+
+    @NotNull(message = "算法ID不能为空")
+    private Integer algoId;
+    /**
+     * did 数据ID
+     */
+    @NotNull (message = "数据ID不能为空")
+    private String did;
+
+    /**
+     * 实验室ID,和设备编号二选一即可
+     */
+    private Integer labId;
+
+    /**
+     * 设备编号,和实验室ID二选一即可
+     */
+    private String hardwareNum;
+    /**
+     * 算法名称
+     */
+    private String algorithmName;
+
+    /**
+     * 视频流地址
+     */
+    private String streamUrl;
+
+    /**
+     * 间隔时间
+     */
+    private Integer waitTime=1;
+
+}

+ 17 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/CheckResultValid.java

@@ -0,0 +1,17 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.common.core.domain.R;
+
+import java.util.Map;
+
+/**
+ * 检查结果验证
+ * @Author: zhoupan
+ * @Date: 2021/11/14/10:26
+ * @Description:
+ */
+@FunctionalInterface
+public interface CheckResultValid {
+
+    R apply(Map<String,Object>  map);
+}

+ 544 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/CheckService.java

@@ -0,0 +1,544 @@
+package com.zd.alg.forward.serivce;
+
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.zd.alg.forward.utils.Base64DecodedMultipartFile;
+import com.zd.alg.forward.utils.FileUploadUtils;
+import com.zd.alg.forward.utils.HttpUtils;
+import com.zd.common.core.constant.Constants;
+import com.zd.common.core.constant.SecurityConstants;
+import com.zd.common.core.domain.R;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.common.core.utils.ServletUtils;
+import com.zd.common.core.utils.StringUtils;
+import com.zd.common.redis.service.RedisService;
+import com.zd.alg.forward.config.AlgorithmYml;
+import com.zd.alg.forward.domain.AnalysisData;
+import com.zd.alg.forward.domain.DataPostAnalysisRespDto;
+import com.zd.alg.forward.domain.ImgPostResponse;
+import com.zd.alg.forward.domain.VideoRequestData;
+import com.zd.alg.forward.serivce.mqtt.CommonSend;
+import com.zd.system.api.RemoteFileService;
+import com.zd.system.api.alarm.RemoteAlarmService;
+import com.zd.system.api.alarm.domain.AlarmEntrty;
+import com.zd.system.api.alarm.domain.Routes;
+import com.zd.system.api.alarm.domain.SendTypes;
+import com.zd.system.api.domain.Algorithm;
+import com.zd.system.api.domain.SysFile;
+import com.zd.system.api.laboratory.RemoteLaboratoryService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+import sun.misc.BASE64Decoder;
+import zd.common.mqtt.config.MessageBody;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.*;
+
+@Service
+@RefreshScope
+public class CheckService {
+    Logger logger = LoggerFactory.getLogger(CheckService.class);
+    @Resource(name = "restTemplateLocal")
+    private RestTemplate restTemplateLocal;
+    /**
+     * 上传文件存储在本地的根路径
+     */
+    @Value("${file.path:/home/AIPIC}")
+    private String localFilePath;
+
+    @Autowired
+    private AlgorithmYml algorithmYml;
+
+    @Autowired
+    private RedisService redisService;
+
+    @Autowired
+    private RemoteAlarmService remoteAlarmService;
+
+    @Autowired
+    private CommonSend commonSend;
+
+    @Autowired
+    private SendSginAccessLogService sendSginAccessLogService;
+
+    @Autowired
+    private RemoteFileService remoteFileService;
+
+    @Autowired
+    private RemoteLaboratoryService laboratoryService;
+
+
+    private static final String warn = "违规操作告警:请{},离开实验室!";
+
+    /**
+     * 合并检查 三合一套餐
+     */
+    public R checkAndCommit(Long id, MultipartFile file, Long subId) {
+        try {
+            int alarmNum = 0;
+            //========= 请求超时验证部分开始 ===========
+            //600 则代表退出验证流程 需要重新刷卡
+//            R<Long> fail = getObjectR(id);
+//            if (fail.getCode() != 200) {
+//                return fail;
+//            }
+            //根据实验室id查询检查项
+            R<Map<Object, Object>> subject = laboratoryService.getCheckInfo(subId);
+            if (subject.getCode() != 200) {
+                return subject;
+            }
+            Map<Object, Object> map = subject.getData();
+            Object labCheckInObj = map.get("checkIn");
+            if (StringUtils.isNull(labCheckInObj)) {
+                return R.fail(700, "未配置检查项");
+            }
+            String labSkipped = "0";
+            String labCheckCount = "2";
+            Object labSkippedObj = map.get("skipped");
+            if (StringUtils.isNotNull(labSkippedObj)) {
+                labSkipped = String.valueOf(labSkippedObj);
+            }
+            Object labCheckCountObj = map.get("checkCount");
+            if (StringUtils.isNotNull(labCheckCountObj)) {
+                labCheckCount = String.valueOf(labCheckCountObj);
+            }
+            String labCheckIn = String.valueOf(labCheckInObj);
+            String[] checkItem = labCheckIn.split(",");
+            File toFile = HttpUtils.multipartFileToFile(file);
+            for (String code : checkItem) {
+                //========= 获取算法INFO ===========
+                AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(code));
+                MultiValueMap<String, Object> params = HttpUtils.getStringObjectMultiValueMap(checkValid, String.valueOf(id));
+                HttpEntity<MultiValueMap<String, Object>> files = HttpUtils.getHttpEntityMap(toFile, params);
+                logger.info("===============4=================");
+                ImgPostResponse<DataPostAnalysisRespDto> send = HttpUtils.send(restTemplateLocal, files, algorithmYml);
+                if (send == null || send.getStatus_code() != 1000) {
+                    assert send != null;
+                    logger.error(send.getMessage());
+                    return R.fail("算法服务错误,请重试!");
+                }
+                logger.info("===============5=================");
+                R algorithm = saveAlgorithm(send, checkValid);
+                if (algorithm.getCode() != 200) return algorithm;
+                DataPostAnalysisRespDto data = send.getData();
+
+                Map<String, Object> result = (Map<String, Object>) data.getAnalysisDatas().get(0).getResult();
+                Map<String, Object> algorithmData = (Map<String, Object>) result.get("algorithm_data");
+                Map<String, Object> modelResult = (Map<String, Object>) result.get("model_data");
+                List<Map<String, Object>> objects = (List<Map<String, Object>>) modelResult.get("objects");
+                logger.info("============算法请求日志打印:算法ID:{},请求结果:{}", code, algorithmData.getOrDefault("is_alert", ""));
+                //通过的
+                if (algorithmData.getOrDefault("is_alert", "").toString().equals("false") && !objects.isEmpty()) {
+                    alarmNum++;
+                } else {
+                    //不跳过
+                    if (StringUtils.isNotEmpty(labSkipped) && labSkipped.equals("1")) {
+                        //如果没有通过则次数加一
+                        //键为前缀+签到id +下划线+验证类型
+                        String key = Constants.SINGIN_CHECK_JUMP_KEY + id + "_" + code;
+                        Long increment = redisService.redisTemplate.opsForValue().increment(key);
+                        redisService.expire(key, Constants.SINGIN_OUT_TIME);
+                        if (increment != null && increment >= Integer.parseInt(labCheckCount)) {
+                            //黎晨这里让把跳过时状态码改为700,所以700的含义为检查失败并且跳过
+                            return R.fail(700, "符合跳过条件执行跳过");
+                        }
+                    }
+                    return R.fail(300, "算法识别未通过", code);
+                }
+                Boolean f = send.getStatus_code() == 1000;
+                String msg = f ? "解析成功!" : "解析失败!";
+                send(code, id, f, msg);
+            }
+            if (alarmNum == checkItem.length) {
+                return R.ok();
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage());
+            e.printStackTrace();
+        }
+        return R.fail();
+    }
+
+    /**
+     * @param id 进出记录ID
+     */
+    public R checkAndCommit(String code, MultipartFile file, Long id) {
+        //========= 请求超时验证部分开始 ===========
+        //600 则代表退出验证流程 需要重新刷卡
+        R<Long> fail = getObjectR(id);
+        if (fail.getCode() != 200) {
+            return fail;
+        }
+        //========= 请求超时验证部分结束 ===========
+        //========= 获取算法INFO ===========
+        AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(code));
+        //=========发送验证信息到算法服务开始
+        JSONObject send = null;
+        try {
+            send = send(file, checkValid, id);
+        } catch (Exception e) {
+            logger.error(e.getMessage());
+            e.printStackTrace();
+        }
+        //=========发送验证信息到算法服务结束
+        //=========算法服务返回结果验证开始
+        R apply = R.fail("算法服务错误,请重试!");
+        Boolean f = false;
+        try {
+            if (send == null || send.getInteger("status_code") != 1000L) {
+                apply = R.fail("算法服务错误,请重试!");
+                return apply;
+            }
+            Map<String, Object> result = null;
+            try {
+                result = send.getJSONObject("data").getJSONObject("result").getInnerMap();
+            } catch (Exception e) {
+                e.printStackTrace();
+                apply = R.fail("算法服务返回数据错误,请联系管理员!");
+                return apply;
+            }
+            //todo  获取 检测结果
+            apply = checkValid.getCheckResultValid().apply(result);
+            f = apply.getCode() == 200;
+            if (!f && algorithmYml.getJumpThreshold() != -1) {
+                //如果没有通过则次数加一
+                //键为前缀+签到id +下划线+验证类型
+                String key = Constants.SINGIN_CHECK_JUMP_KEY + id + "_" + code;
+                Long increment = redisService.redisTemplate.opsForValue().increment(key);
+                redisService.expire(key, Constants.SINGIN_OUT_TIME);
+                if (increment >= algorithmYml.getJumpThreshold()) {
+                    //黎晨这里让把跳过时状态码改为700,所以700的含义为检查失败并且跳过
+                    apply.setCode(700);
+                }
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            apply = R.fail("算法服务返回数据错误,请联系管理员!");
+            return apply;
+        } finally {
+//            //无论成功失败,插入记录数据,异步请求远程接口
+            send(code, id, f, apply.getMsg());
+//            //保存识别记录
+//            String r = send == null ? null : send.toJSONString();
+//           sendSginAccessLogService.saveAlgorithmResult(cereataAlgorithm(r, "image", f, apply.getMsg(), id, fail.getData()));
+//            //持久化
+//            Map<String, Object> data = (Map<String, Object>) send.get("data");
+//           /* Object srcImage =  data.get("src_image");
+//            String srcImg = this.generateImage(srcImage.toString(),algorithmYml.getImgTemp());*/
+//            data.put("src_img", fileR.getData().getUrl());
+//            data.put("type", "image");
+//            data.put("algorithmName", checkValid.getAlgorithmName());
+////            restTemplateLocal.postForEntity("http://192.168.1.17:9218/algorithm/save",data,String.class).getBody();
+//            laboratoryService.saveData(Covert(data));
+        }
+        //=========算法服务返回结果验证结束
+        logger.info(apply.toString());
+        if (apply.getCode() == 200) {
+            //加奖励分
+            // laboratoryService.addRecord();
+        }
+        return apply;
+    }
+
+
+    /**
+     * 三合一
+     *
+     * @param ids
+     * @param file
+     * @param codes
+     * @return
+     */
+    public R checkInAll(Long[] ids, MultipartFile file[], String codes) {
+        Map result = new HashMap();
+        String[] codesArrs = codes.split(",");
+        for (int i = 0; i < codesArrs.length; i++) {
+            R r = checkAndCommit(codesArrs[i], file[i], ids[i]);
+            AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(codesArrs[i]));
+            result.put(checkValid.getAlgoId(), r.getMsg() == null ? "通过" : r.getMsg());
+
+        }
+        return R.ok(result);
+    }
+
+
+    public Algorithm Covert(Map data) {
+        Algorithm algorithm = new Algorithm();
+        algorithm.setAlgorithmType(data.get("type").toString());
+        algorithm.setAlgorithmResult(data.get("src_img").toString());
+        algorithm.setSubId(Long.valueOf(data.get("aid").toString()));
+        algorithm.setSignId(Long.valueOf(data.get("did").toString()));
+        algorithm.setAlgorithmName(data.get("algorithmName").toString());
+        algorithm.setIsAlarm(Integer.parseInt(data.get("isAlarm").toString()));
+        algorithm.setStatus(0);
+        return algorithm;
+    }
+
+    /**
+     * @param algorithmResult
+     * @param type
+     * @param result
+     * @param msg
+     * @param signId          签到ID
+     * @param subId           实验室ID
+     * @return
+     */
+    public Algorithm cereataAlgorithm(String algorithmResult, String type, Boolean result, String msg, Long signId, Long subId) {
+        Algorithm algorithm = new Algorithm();
+        algorithm.setAlgorithmResult(algorithmResult);
+        algorithm.setAlgorithmType(type);
+        algorithm.setParseResult(result);
+        algorithm.setParseResultMsg(msg);
+        algorithm.setSignId(signId);
+        algorithm.setSubId(subId);
+        return algorithm;
+
+    }
+
+    /**
+     * 视频检查
+     *
+     * @param checkValid
+     * @param videoRequestData
+     * @param subId
+     */
+    public void checkVideo(AlgorithmYml.VideoCheckValid checkValid, VideoRequestData videoRequestData, Long subId) {
+        boolean f = false;
+        R apply = R.fail("结果解析错误");
+        try {
+            apply = checkValid.getVideoCheckResultValid().apply(videoRequestData.getData(), subId);
+            if (f = (apply.getCode() == 300)) {
+                //发送预警
+                MessageBody<String> messageBody = new MessageBody<>();
+                messageBody.setData(CharSequenceUtil.format(warn, checkValid.getTips()));
+                //给一体机发送mqtt消息
+                commonSend.send("lab/news" + subId, messageBody, 0);
+                //获取实验室负责人电话 需要远程获取,因为在内网,没有和实验室服务在一起, 不能用feign,需要手动通过网关请求
+                String adminPhoneBySubId = sendSginAccessLogService.getAdminPhoneBySubId(subId, algorithmYml.getLoginUri());
+                if (CharSequenceUtil.isNotBlank(adminPhoneBySubId)) {
+                    AlarmEntrty alarmEntrty = new AlarmEntrty(Routes.NoticePush, new String[]{SendTypes.SMS.toString()}, adminPhoneBySubId);
+                    //发送短信 通过feign 调用
+                    remoteAlarmService.send(alarmEntrty);
+                }
+            }
+        } finally {
+            String json = JSON.toJSONString(videoRequestData);
+            sendSginAccessLogService.saveAlgorithmResult(cereataAlgorithm(json, "vidoe", f, apply.getMsg(), null, subId));
+            Map<String, Object> data = (Map<String, Object>) JSONObject.parse(json);
+            Object srcImage = data.get("pic_data");
+            String srcImg = this.generateImage(srcImage.toString(), algorithmYml.getImgTemp());
+            data.put("src_img", srcImg);
+            data.put("type", "video");
+            laboratoryService.saveData(Covert(data));
+        }
+    }
+
+    /**
+     * 发送警报
+     */
+    public void sendAlarm(VideoRequestData videoRequestData) {
+        sendSginAccessLogService.saveAlarm(videoRequestData);
+    }
+
+    /**
+     * 发送警报
+     */
+    public void playMp3() {
+        sendSginAccessLogService.playMp3();
+    }
+
+
+    private R<Long> getObjectR(Long id) {
+        Long subId = redisService.getCacheObject(Constants.SINGIN_id_KEY + id);
+        if (subId == null) {
+            return R.fail(600, "签到&签出已超时,请重新刷卡重试!");
+        }
+        //刷新key
+        boolean expire = redisService.expire(Constants.SINGIN_id_KEY + id, 120);
+        if (!expire) {
+            return R.fail(600, "签到&签出已超时,请重新刷卡重试!");
+        }
+        return R.ok(subId);
+    }
+
+    //给黎晨用的模拟方法 他让给他mock个方法测试
+    public R mockTest(String code, MultipartFile file, Long id) {
+        R<Long> objectR = getObjectR(id);
+        if (objectR.getCode() != 200) {
+            return objectR;
+        }
+        //随机成功或失败
+        Boolean f = RandomUtil.randomInt(0, 2) == 0;
+        //无论成功失败,插入记录数据,异步请求远程接口
+        send(code, id, f, "");
+        if (f) {
+            return R.ok();
+        }
+        //如果没有通过则次数加一
+        Map<String, Boolean> map = new HashMap(1);
+        //键为前缀+签到id +下划线+验证类型
+        String key = Constants.SINGIN_CHECK_JUMP_KEY + id + "_" + code;
+        Long increment = redisService.redisTemplate.opsForValue().increment(key);
+        redisService.expire(key, Constants.SINGIN_OUT_TIME);
+        Boolean isJump = increment >= algorithmYml.getJumpThreshold();
+        map.put("jump", isJump);
+        R<Object> fail = R.fail();
+        fail.setData(map);
+        return fail;
+
+    }
+
+
+    public void send(String code, Long id, Boolean f, String msg) {
+        String token = Objects.requireNonNull(ServletUtils.getRequest()).getHeader(SecurityConstants.TOKEN_AUTHENTICATION);
+        if (CharSequenceUtil.isBlank(token)) {
+            throw new ServiceException("无权限!");
+        }
+        sendSginAccessLogService.sendAddLogRest(code, id, f, msg, token, algorithmYml.getLoginUri() + algorithmYml.getCheckLogUrl());
+    }
+
+    /**
+     * 获取算法请求地址
+     *
+     * @param checkValid
+     * @param id
+     * @return
+     */
+    private String getPostUrl(AlgorithmYml.CheckValid checkValid, Long id) {
+        //设置请求体,注意是LinkedMultiValueMap
+        MultiValueMap<String, Object> params = HttpUtils.getStringObjectMultiValueMap(checkValid, String.valueOf(id));
+        Set<String> keySet = params.keySet();
+        StringBuffer stb = new StringBuffer();
+        for (String key : keySet) {
+            stb.append("&" + key + "=" + params.get(key).get(0).toString());
+        }
+        String paramUrl = algorithmYml.getImgPostUrl() + String.valueOf(stb).replaceFirst("&", "?");
+        logger.info(paramUrl);
+        return paramUrl;
+    }
+
+    /**
+     * 图片算法请求参数
+     *
+     * @param file
+     * @param checkValid
+     * @return
+     * @throws IOException
+     */
+
+    public JSONObject send(MultipartFile file, AlgorithmYml.CheckValid checkValid, Long id) throws IOException {
+        //设置请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+        File uploadFile = null;
+        try {
+            // 文件本地存储收集
+            FileUploadUtils.upload(localFilePath, file);
+            //MultipartFile 转为临时文件
+            uploadFile = HttpUtils.multipartFileToFile(file);
+            //文件转为文件系统资源
+            FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
+            //设置请求体,注意是LinkedMultiValueMap
+            MultiValueMap<String, Object> params = HttpUtils.getStringObjectMultiValueMap(checkValid, String.valueOf(id));
+
+            MultiValueMap<String, Object> form = HttpUtils.getStringObjectMultiValueMap(fileSystemResource);
+            params.addAll(form);
+            //用HttpEntity封装整个请求报文
+            HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(params, headers);
+            String paramUrl = algorithmYml.getImgPostUrl();
+            logger.info(paramUrl);
+            JSONObject body = restTemplateLocal.postForObject(paramUrl, files, JSONObject.class);
+            logger.info(body.toJSONString());
+            return body;
+        } finally {
+            //删除临时文件
+//            if (uploadFile != null) {
+//                uploadFile.delete();
+//            }
+        }
+    }
+
+    /**
+     * base64转图片/视频
+     *
+     * @param imgStr      base64位图片
+     * @param imgFilePath 路径
+     * @return
+     */
+    public String generateImage(String imgStr, String imgFilePath) {// 对字节数组字符串进行Base64解码并生成图片
+        BASE64Decoder decoder = new BASE64Decoder();
+        try {
+            // Base64解码
+            byte[] bytes = decoder.decodeBuffer(imgStr);
+            for (int i = 0; i < bytes.length; ++i) {
+                if (bytes[i] < 0) {// 调整异常数据
+                    bytes[i] += 256;
+                }
+            }
+            // 生成jpeg图片
+            imgFilePath += "/" + UUID.randomUUID() + ".jpeg";
+            OutputStream out = new FileOutputStream(imgFilePath);
+            out.write(bytes);
+            out.flush();
+            out.close();
+            return imgFilePath;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 存储数据
+     *
+     * @param send
+     * @param checkValid
+     */
+    private R saveAlgorithm(ImgPostResponse<DataPostAnalysisRespDto> send, AlgorithmYml.CheckValid checkValid) {
+        DataPostAnalysisRespDto analysisRespDto = send.getData();
+        List<AnalysisData> analysisDatas = analysisRespDto.getAnalysisDatas();
+        if (!analysisDatas.isEmpty()) {
+            for (AnalysisData data : analysisDatas) {
+                String picture = data.getRet_image();
+                String header = "data:image/jpeg;base64," + picture;
+                MultipartFile multipartFile = Base64DecodedMultipartFile.base64ToMultipart(header);
+                R<SysFile> sysFileR = remoteFileService.upload(multipartFile);
+                String imageUrl = sysFileR.getData().getUrl();
+                Map<String, Object> result = (Map<String, Object>) data.getResult();
+                Map<String, Object> map = new HashMap<>();
+                Map<String, Object> algorithmData = (Map<String, Object>) result.get("algorithm_data");
+                Map<String, Object> modelResult = (Map<String, Object>) result.get("model_data");
+                List<Map<String, Object>> objects = (List<Map<String, Object>>) modelResult.get("objects");
+                if (algorithmData.getOrDefault("is_alert", "").toString().equals("false") && objects.size() > 0) {
+                    map.put("isAlarm", 0);
+                } else {
+                    map.put("isAlarm", 1);
+                }
+                map.put("did", checkValid.getDid());
+                map.put("aid", checkValid.getAlgoId());
+                map.put("src_img", imageUrl);
+                map.put("type", "image");
+                map.put("algorithmName", checkValid.getAlgorithmName());
+                return laboratoryService.saveData(Covert(map));
+            }
+        }
+        logger.error("接口数据返回异常");
+        throw new ServiceException("接口数据返回异常");
+    }
+}

+ 126 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/FireImageService.java

@@ -0,0 +1,126 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.alg.forward.properties.FireProperties;
+import com.zd.alg.forward.utils.HttpUtils;
+import com.zd.alg.forward.utils.VideoUtils;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.alg.forward.config.AlgorithmYml;
+import com.zd.alg.forward.domain.AnalysisData;
+import com.zd.alg.forward.domain.DataPostAnalysisRespDto;
+import com.zd.alg.forward.domain.ImgPostResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.springframework.http.HttpEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 火焰图片抓拍处理
+ *
+ * @author Administrator
+ */
+@Service
+@Slf4j
+public class FireImageService {
+
+    @Resource
+    private FireProperties fireProperties;
+    @Resource
+    private AlgorithmYml algorithmYml;
+
+    @Resource
+    private RestTemplate restTemplate;
+    @Resource
+    private SendSginAccessLogService sendSginAccessLogService;
+
+    /**
+     * 算法接口返回值
+     */
+    private static final Integer SUCCESS_CODE = 1000;
+
+    /**
+     * 生成的图片的类型
+     */
+    private static final String IMAGE_FORMAT = "jpg";
+
+    public void catchImage() throws IOException {
+        String streamUrl = fireProperties.getStreamUrl();
+        if (streamUrl == null) {
+            throw new ServiceException("未配置流媒体地址");
+        }
+
+        try (FFmpegFrameGrabber grabber = VideoUtils.createGrabber(streamUrl)) {
+            grabber.start();
+            String fileName = "test";
+            //文件储存对象
+            File file=new File(fileName+"." + IMAGE_FORMAT);
+            //获取第一帧
+            Frame frame = grabber.grabFrame();
+            if (frame != null) {
+                //视频快照
+                frame = grabber.grabImage();
+                BufferedImage bufferedImage = VideoUtils.frameToBufferedImage(frame);
+                if (bufferedImage != null) {
+                    ImageIO.write(bufferedImage, IMAGE_FORMAT, file);
+                }
+                log.info("图片地址为[{}]", file.getAbsoluteFile());
+                grabber.stop();
+                send(file);
+            }
+        }
+    }
+
+    public boolean catchImage(MultipartFile file) {
+        try {
+            File toFile = HttpUtils.multipartFileToFile(file);
+            send(toFile);
+            return true;
+        } catch (IOException e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    private void send(File file) {
+        if (fireProperties.getAlgoId() == null) {
+            throw new ServiceException("未配置火焰算法ID");
+        }
+        MultiValueMap<String, Object> params = HttpUtils.getMultiValueMap(fireProperties, null);
+        HttpEntity<MultiValueMap<String, Object>> files = HttpUtils.getHttpEntityMap(file, params);
+        ImgPostResponse<DataPostAnalysisRespDto> send = HttpUtils.send(restTemplate, files, algorithmYml);
+        if (send == null || send.getStatus_code() != SUCCESS_CODE) {
+            assert send != null;
+            log.error(send.getMessage());
+            return;
+        }
+        DataPostAnalysisRespDto data = send.getData();
+
+        List<AnalysisData> analysisDatas = data.getAnalysisDatas();
+        AnalysisData analysisData = analysisDatas.get(0);
+        int code = analysisData.getCode();
+        if (code==-1){
+            log.error("==============请求失败:{}=================",analysisData.getMsg());
+        }else {
+            log.info("===============向算法服务发送数据完成====================");
+            Map<String, Object> result = (Map<String, Object>) Optional.ofNullable(analysisData.getResult()).orElse(Collections.emptyMap());
+            Map<String, Object> algorithmData = (Map<String, Object>) Optional.ofNullable(result.get("algorithm_data")).orElse(Collections.emptyMap());
+            boolean alert = "false".equals(algorithmData.getOrDefault("is_alert", "").toString());
+            if (!alert) {
+                log.info("===============返回告警信息====================");
+                sendSginAccessLogService.saveAlarm(fireProperties);
+            }
+        }
+    }
+}

+ 31 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/ImageCheckResultValidImpl.java

@@ -0,0 +1,31 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.common.core.domain.R;
+
+import java.util.Map;
+
+/**
+ * 图片源POST:算法结果解析
+ * @Author: zhoupan
+ * @Date: 2021/11/14/12:54
+ * @Description:
+ */
+public class ImageCheckResultValidImpl implements CheckResultValid{
+    @Override
+    public R apply(Map<String, Object> map) {
+
+        try {
+            Map<String, Object>  algorithm_data = (Map<String, Object>)map.get("algorithm_data");
+            Boolean is_alert = (Boolean)algorithm_data.get("is_alert");
+
+
+            //是不是先要判断人数??????target_count
+            if(is_alert) return R.fail("检查不通过!");
+            //判断是否是多个目标等 返回详细失败结果
+
+            return R.ok();
+        } catch (Exception e) {
+            return R.fail("结果解析错误!");
+        }
+    }
+}

+ 78 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/ImageService.java

@@ -0,0 +1,78 @@
+package com.zd.alg.forward.serivce;
+
+
+import com.zd.alg.forward.utils.VideoUtils;
+import com.zd.common.core.constant.HttpStatus;
+import com.zd.common.core.domain.R;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.system.api.RemoteFileService;
+import com.zd.system.api.domain.SysFile;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.entity.ContentType;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.rmi.ServerException;
+
+/**
+ * @author hanson
+ */
+@Service
+@Slf4j
+public class ImageService {
+
+    @Resource
+    private RemoteFileService remoteFileService;
+
+    /**
+     * 生成的图片的类型
+     */
+    private static final String IMAGE_FORMAT = "jpg";
+
+    public SysFile photograph(String streamUrl) throws IOException {
+        try (FFmpegFrameGrabber grabber = VideoUtils.createGrabber(streamUrl)) {
+            grabber.start();
+            String fileName = "test";
+            //文件储存对象
+            File file = new File(fileName + "." + IMAGE_FORMAT);
+            //获取第一帧
+            Frame frame = grabber.grabFrame();
+            if (frame != null) {
+                //视频快照
+                frame = grabber.grabImage();
+                BufferedImage bufferedImage = VideoUtils.frameToBufferedImage(frame);
+                if (bufferedImage != null) {
+                    ImageIO.write(bufferedImage, IMAGE_FORMAT, file);
+                }
+                log.info("图片地址为[{}]", file.getAbsoluteFile());
+                grabber.stop();
+                MultipartFile multipartFile = getMultipartFile(file);
+                R<SysFile> upload = remoteFileService.upload(multipartFile);
+                if (upload.getCode() == HttpStatus.SUCCESS && upload.getData() != null) {
+                    return upload.getData();
+                }else {
+                    throw new ServerException(upload.getMsg());
+                }
+            }
+        }
+        return null;
+    }
+
+    private MultipartFile getMultipartFile(File file) {
+        try(FileInputStream fileInputStream = new FileInputStream(file)) {
+            return new MockMultipartFile(file.getName(),file.getName(),
+                    ContentType.APPLICATION_OCTET_STREAM.toString(),fileInputStream);
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+}

+ 103 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/LoginService.java

@@ -0,0 +1,103 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.common.core.constant.SecurityConstants;
+import com.zd.common.core.domain.R;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.common.security.service.TokenService;
+import com.zd.alg.forward.config.AlgorithmYml;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@EnableScheduling
+@Slf4j
+public class LoginService {
+
+
+    @Autowired
+    AlgorithmYml algorithmYml;
+    @Autowired
+    TokenService tokenService;
+
+    private String token;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    /**
+     * 登录获取token
+     * 优化建议, 不要每次请求都登录, 需要本地维护, 定时刷新token,只有访问过期时再重试登录请求
+     */
+    public void loginLab() {
+        //不再重复登录
+        RestTemplate restTemplate = new RestTemplate();
+        //创建请求头
+        String url = algorithmYml.getLoginUri() + "auth/one/login";
+        Map<String, String> map = new HashMap<>(2);
+        map.put("username", "admin");
+        map.put("password", "zd123456..");
+        ParameterizedTypeReference<R<Map<String, Object>>> reference = new ParameterizedTypeReference<R<Map<String, Object>>>(){};
+        try {
+            ResponseEntity<R<Map<String, Object>>> response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(map), reference);
+            R<Map<String, Object>> parse = response.getBody();
+            if (parse != null) {
+                Assert.isTrue(parse.getCode() == R.SUCCESS, "登录失败:" + parse.getMsg());
+                //拿到token
+                Map<String, Object> data = parse.getData();
+                setToken("Bearer " + data.get("access_token"));
+            }
+        } catch (RestClientException e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+
+    public String getCacheToken() {
+        //换取缓存的token
+        return getToken();
+    }
+
+    /**
+     * 刷新key
+     */
+    public void refreshToken() {
+        try {
+            String tokenValue = token.replace(SecurityConstants.TOKEN_PREFIX, "");
+            tokenService.refreshToken(tokenValue);
+        } catch (Exception e) {
+            loginLab();
+        }
+    }
+
+    /**
+     * 每隔30分钟刷新次token
+     */
+    @Scheduled(cron = "0 */30 * * * ?")
+    //或直接指定时间间隔,例如:5秒
+    //@Scheduled(fixedRate=5000)
+    private void configureTasks() {
+        log.info("第一次执行!!");
+        if (token != null) {
+            refreshToken();
+        }
+    }
+
+}

+ 58 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/PeopleCheckResultValidImpl.java

@@ -0,0 +1,58 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.common.core.domain.R;
+import com.zd.alg.forward.config.AlgorithmYml;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+/**
+ * 视频源:人数结果解析
+ * @Author: zhoupan
+ * @Date: 2021/11/14/12:54
+ * @Description:
+ */
+@Service
+public class PeopleCheckResultValidImpl implements VideoCheckResultValid{
+
+    @Autowired
+    SendSginAccessLogService sendSginAccessLogService;
+
+    @Autowired
+    AlgorithmYml algorithmYml;
+
+//    private void initAttr()
+//    {
+//        sendSginAccessLogService= SpringUtils.getBean(SendSginAccessLogService.class);
+//        algorithmYml = SpringUtils.getBean(AlgorithmYml.class);
+//    }
+
+    public PeopleCheckResultValidImpl() {
+    }
+
+    @Override
+    public R apply(Map<String, Object> map, Long subId) {
+        try {
+            Map<String, Object>  algorithm_data = (Map<String, Object>)map.get("algorithm_data");
+            Boolean is_alert = (Boolean)algorithm_data.get("is_alert");
+            //是不是先要判断人数??????target_count
+            if(is_alert)
+            {
+                int target_count = (int)algorithm_data.get("target_count");
+                if(target_count>0)
+                {
+                    Integer count = sendSginAccessLogService.getSubAccessCountBySubId(subId,algorithmYml.getLoginUri());
+                    if(target_count>count)
+                    {
+                        return R.fail(300,"算法识别人数大于实验室人数!");
+                    }
+                }
+            }
+            //判断是否是多个目标等 返回详细失败结果
+            return R.ok();
+        } catch (Exception e) {
+            return R.fail("结果解析错误!");
+        }
+    }
+}

+ 263 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/SendSginAccessLogService.java

@@ -0,0 +1,263 @@
+package com.zd.alg.forward.serivce;
+
+import com.alibaba.fastjson.JSONObject;
+import com.zd.alg.forward.properties.FireProperties;
+import com.zd.common.core.constant.HttpStatus;
+import com.zd.common.core.constant.SecurityConstants;
+import com.zd.common.core.domain.R;
+import com.zd.common.core.utils.StringUtils;
+import com.zd.common.core.web.domain.AjaxResult;
+import com.zd.alg.forward.config.AlgorithmYml;
+import com.zd.alg.forward.domain.VideoRequestData;
+import com.zd.system.api.domain.Algorithm;
+import com.zd.system.api.domain.PlayVo;
+import com.zd.system.api.speak.RemoteSpeakService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.data.redis.core.ListOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.http.*;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import static com.zd.common.core.constant.Constants.MAP_INIT_SIZE;
+
+@Service
+public class SendSginAccessLogService {
+
+    Logger logger = LoggerFactory.getLogger(SendSginAccessLogService.class);
+
+    private final RestTemplate restTemplateLocal;
+    public final RedisTemplate<Object, Object> redisTemplate;
+    private final LoginService loginLab;
+    private final AlgorithmYml algorithmYml;
+
+    private final RemoteSpeakService remoteSpeakService;
+
+    /**
+     * 报警次数记录
+     */
+    static Map<String, Integer> alarmMap = new ConcurrentHashMap<>();
+    /**
+     * 超时时间设置MAP
+     */
+    static Map<String, Long> expiryMap = new ConcurrentHashMap<>();
+
+    @Autowired
+    public SendSginAccessLogService(RestTemplate restTemplateLocal, RedisTemplate<Object, Object> redisTemplate, LoginService loginLab, AlgorithmYml algorithmYml, RemoteSpeakService remoteSpeakService) {
+        this.restTemplateLocal = restTemplateLocal;
+        this.redisTemplate = redisTemplate;
+        this.loginLab = loginLab;
+        this.algorithmYml = algorithmYml;
+        this.remoteSpeakService = remoteSpeakService;
+    }
+
+    @Async
+    public void sendAddLogRest(String code, Long id, Boolean f, String msg, String token, String url) {
+        JSONObject json = new JSONObject();
+        json.put("signId", id);
+        json.put("code", code);
+        json.put("result", f);
+        json.put("msg", msg);
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+        headers.add(SecurityConstants.TOKEN_AUTHENTICATION, token);
+        HttpEntity<String> formEntity = new HttpEntity<>(json.toString(), headers);
+        logger.info("保存检查日志记录,地址:{}", url);
+        String s = restTemplateLocal.postForEntity(url, formEntity, String.class).getBody();
+        logger.info("保存检查日志记录结果:{}", s);
+
+    }
+
+    /**
+     * 通过实验室ID获取实验室负责人电话
+     */
+    public String getAdminPhoneBySubId(Long subId, String url) {
+        ParameterizedTypeReference<R<String>> reference = new ParameterizedTypeReference<R<String>>() {
+        };
+        R<String> send = send(null, url + "laboratory/subject/" + subId + "/admin/phone", HttpMethod.GET, reference);
+        return send != null ? send.getData() : "";
+    }
+
+    public void saveAlarm(VideoRequestData videoRequestData) {
+        String cid = videoRequestData.getCid();
+        if (Boolean.TRUE.equals(redisTemplate.hasKey(cid))) {
+            Integer count = (Integer) redisTemplate.opsForValue().get(cid);
+            if (count == null) {
+                count = 0;
+            }
+            redisTemplate.opsForValue().set(cid, count + 1, 30, TimeUnit.SECONDS);
+            R<Object> r = sendAlarm(videoRequestData);
+            if (r.getCode() != HttpStatus.SUCCESS) {
+                logger.error("警报发送失败,警报类型;{},失败原因:{}", videoRequestData.getAlgo_name(), r.getMsg());
+            }
+        } else {
+            redisTemplate.opsForValue().set(cid, 1, 30, TimeUnit.SECONDS);
+        }
+
+    }
+
+    public void saveAlarm(FireProperties properties) {
+        String hardwareNum = properties.getHardwareNum();
+        long currentTimeMillis = System.currentTimeMillis();
+        if (expiryMap.containsKey(hardwareNum)) {
+            Long expiryTime = expiryMap.get(hardwareNum);
+            if (currentTimeMillis > expiryTime) {
+                expiryMap.remove(hardwareNum);
+                alarmMap.remove(hardwareNum);
+            }else {
+                send(properties, hardwareNum);
+            }
+            int count = alarmMap.get(hardwareNum) == null ? 0 : alarmMap.get(hardwareNum);
+            ++count;
+            alarmMap.put(hardwareNum, count);
+            expiryMap.put(hardwareNum, currentTimeMillis + 30 * 1000);
+            logger.info("已触发火焰警报:{}次",alarmMap.get(hardwareNum));
+        } else {
+            alarmMap.put(hardwareNum, 1);
+            expiryMap.put(hardwareNum, currentTimeMillis + 30 * 1000);
+        }
+    }
+
+    private void send(FireProperties properties, String hardwareNum) {
+        R<Object> r = send(properties);
+        if (r.getCode() != HttpStatus.SUCCESS) {
+            logger.error("火焰警报失败原因:{}", r.getMsg());
+        }
+    }
+
+    public void playMp3() {
+        if (!algorithmYml.isLoudspeakerSwitch()) {
+            return;
+        }
+        String loudspeakerIp1 = algorithmYml.getLoudspeakerIp1();
+        String loudspeakerIp2 = algorithmYml.getLoudspeakerIp2();
+        R deviceList = remoteSpeakService.getDeviceList(1, 10, 5L);
+        if (deviceList.getCode() == HttpStatus.SUCCESS) {
+            List<Map<String, Object>> mapList = (List<Map<String, Object>>) deviceList.getData();
+            for (Map<String, Object> map : mapList) {
+                if (StringUtils.isNotNull(map.get("deviceSn")) && StringUtils.isNotNull(map.get("port"))) {
+                    PlayVo playVo = new PlayVo();
+                    playVo.setSn(map.get("deviceSn") + "");
+                    playVo.setPort(Integer.parseInt(map.get("port") + ""));
+                    sendText(loudspeakerIp1, playVo);
+                    sendText(loudspeakerIp2, playVo);
+                }
+            }
+        }
+    }
+
+    private void sendText(String ip, PlayVo playVo) {
+        String url = algorithmYml.getTargetUrl() + "/zd-speaker/speaker/textPlayMusic?ip={ip}&texts={texts}";
+        HttpEntity<PlayVo> requestEntity = getMapHttpEntity(playVo);
+        if (StringUtils.isNotEmpty(ip)) {
+            List<String> texts = new ArrayList<>();
+            texts.add("当前环境监测到无人值守");
+            ResponseEntity<AjaxResult> response = restTemplateLocal.postForEntity(url, requestEntity, AjaxResult.class, ip, texts);
+            logger.info("响应码:{},响应结果:{}", response.getStatusCode(), response.getBody());
+        }
+    }
+
+    /**
+     * 发送火焰警报
+     */
+    public R<Object> sendAlarm(VideoRequestData videoRequestData) {
+        Map<String, Object> requestMap = new HashMap<>(MAP_INIT_SIZE);
+        List<Map<String, Object>> maps = new ArrayList<>();
+        Integer aid = videoRequestData.getAid();
+        Map<Integer, AlgorithmYml.AlarmConfig> alarmConfigMap = algorithmYml.getAlarmConfigMap();
+        if (alarmConfigMap.containsKey(aid)) {
+            AlgorithmYml.AlarmConfig alarmConfig = alarmConfigMap.get(aid);
+            Map<String, Object> params = new HashMap<>(MAP_INIT_SIZE);
+            params.put("hardwareNum", videoRequestData.getCid());
+            params.put("val", videoRequestData.getStatus());
+            return getParams(requestMap, maps, alarmConfig, params);
+        }
+        return R.fail("未找到算法");
+    }
+
+    /**
+     * 发送火焰警报
+     */
+    public R<Object> send(FireProperties properties) {
+        Map<String, Object> requestMap = new HashMap<>(MAP_INIT_SIZE);
+        List<Map<String, Object>> maps = new ArrayList<>();
+        Integer aid = properties.getAlgoId();
+        Map<Integer, AlgorithmYml.AlarmConfig> alarmConfigMap = algorithmYml.getAlarmConfigMap();
+        AlgorithmYml.AlarmConfig alarmConfig = alarmConfigMap.get(aid);
+        Map<String, Object> params = new HashMap<>(MAP_INIT_SIZE);
+        params.put("hardwareNum", properties.getHardwareNum());
+        params.put("subId", properties.getLabId());
+        params.put("val", 1);
+        return getParams(requestMap, maps, alarmConfig, params);
+    }
+
+    private R<Object> getParams(Map<String, Object> requestMap, List<Map<String, Object>> maps, AlgorithmYml.AlarmConfig alarmConfig, Map<String, Object> params) {
+        params.put("funNum", alarmConfig.getFunNum());
+        params.put("describe", alarmConfig.getDescribe());
+        maps.add(params);
+        requestMap.put("functionStatuses", maps);
+        ParameterizedTypeReference<R<Object>> reference = new ParameterizedTypeReference<R<Object>>() {
+        };
+        return send(requestMap, algorithmYml.getTargetUrl() + "laboratory/plan/triggerRiskPlan", HttpMethod.POST, reference);
+    }
+
+    private <T> R<T> send(Map<String, Object> params, String url, HttpMethod method, ParameterizedTypeReference<R<T>> reference) {
+        HttpEntity<Map<String, Object>> requestEntity = getMapHttpEntity(params);
+        ResponseEntity<R<T>> exchange = restTemplateLocal.exchange(url, method, requestEntity, reference);
+        R<T> body = exchange.getBody();
+        if (body != null && body.getCode() == HttpStatus.UNAUTHORIZED) {
+            requestEntity = getMapHttpEntityLogin(params);
+            exchange = restTemplateLocal.exchange(url, method, requestEntity, reference);
+
+        }
+        body = exchange.getBody();
+        return body;
+    }
+
+    /**
+     * 保存算法结果
+     */
+    public void saveAlgorithmResult(Algorithm algorithm) {
+        ListOperations<Object, Object> listOperations = redisTemplate.opsForList();
+        listOperations.leftPush("AlgorithmResult:", algorithm);
+    }
+
+
+    /**
+     * 通过实验室ID获取实验室正常打卡人数
+     */
+    public Integer getSubAccessCountBySubId(Long subId, String url) {
+        ParameterizedTypeReference<R<Integer>> reference = new ParameterizedTypeReference<R<Integer>>() {
+        };
+        R<Integer> send = send(null, url + "laboratory/subject/manger/records/" + subId + "/count", HttpMethod.GET, reference);
+        return send == null ? 0 : send.getData();
+    }
+
+    private <T> HttpEntity<T> getMapHttpEntity(T params) {
+        String token = loginLab.getToken();
+        HttpHeaders headers = new HttpHeaders();
+        headers.add(SecurityConstants.TOKEN_AUTHENTICATION, token);
+        return new HttpEntity<>(params, headers);
+    }
+
+    private <T> HttpEntity<T> getMapHttpEntityLogin(T params) {
+        loginLab.loginLab();
+        String token = loginLab.getToken();
+        HttpHeaders headers = new HttpHeaders();
+        headers.add(SecurityConstants.TOKEN_AUTHENTICATION, token);
+        return new HttpEntity<>(params, headers);
+    }
+}

+ 18 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/ShiYanFuCheckResultValidImpl.java

@@ -0,0 +1,18 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.common.core.domain.R;
+
+import java.util.Map;
+
+/**
+ * 实验服结果解析 没用!!!!!!!
+ * @Author: zhoupan
+ * @Date: 2021/11/14/12:54
+ * @Description:
+ */
+public class ShiYanFuCheckResultValidImpl implements CheckResultValid{
+    @Override
+    public R apply(Map<String, Object> map) {
+        return R.ok();
+    }
+}

+ 17 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/VideoCheckResultValid.java

@@ -0,0 +1,17 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.common.core.domain.R;
+
+import java.util.Map;
+
+/**
+ * 检查结果验证
+ * @Author: zhoupan
+ * @Date: 2021/11/14/10:26
+ * @Description:
+ */
+@FunctionalInterface
+public interface VideoCheckResultValid {
+
+    R apply(Map<String,Object>  map,Long subId);
+}

+ 31 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/VideoCheckResultValidImpl.java

@@ -0,0 +1,31 @@
+package com.zd.alg.forward.serivce;
+
+import com.zd.common.core.domain.R;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * 视频源:算法结果解析 发现视频和图片的判断一样 嘿嘿 那就不用这玩意了
+ * 算了还是用吧 ,不然还得处理注入的问题
+ * @Author: zhoupan
+ * @Date: 2021/11/14/12:54
+ * @Description:
+ */
+@Component
+public class VideoCheckResultValidImpl implements VideoCheckResultValid{
+
+    @Override
+    public R apply(Map<String, Object> map, Long subId) {
+        try {
+            Map<String, Object>  algorithm_data = (Map<String, Object>)map.get("algorithm_data");
+            Boolean is_alert = (Boolean)algorithm_data.get("is_alert");
+            //是不是先要判断人数??????target_count
+            if(is_alert) return R.fail(300,"检查不通过!");
+            //判断是否是多个目标等 返回详细失败结果
+            return R.ok();
+        } catch (Exception e) {
+            return R.fail("结果解析错误!");
+        }
+    }
+}

+ 46 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/mqtt/CommonSend.java

@@ -0,0 +1,46 @@
+package com.zd.alg.forward.serivce.mqtt;
+
+import com.alibaba.fastjson.JSON;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import zd.common.mqtt.config.MessageBody;
+
+/**
+ * 公用发送类
+ * @Author: zhoupan
+ * @Date: 2021/10/08/10:42
+ * @Description:
+ */
+@Service
+@ConditionalOnProperty(prefix = "sys.mqtt" ,name = "outbound",havingValue = "true",matchIfMissing = true)
+public class CommonSend {
+
+    @Autowired
+    MqttProducer mqttProducer;
+
+
+    /**
+     * 发送消息:去重模式
+     * @param topic
+     * @param messageBody
+     */
+    public  void  send(String topic, MessageBody messageBody)
+    {
+        mqttProducer.sendToMqtt(topic, 2, JSON.toJSONString(messageBody));
+    }
+
+    /**
+     * 发送消息
+     * @param topic
+     * @param messageBody
+     * @param QOS
+     */
+    public  void  send(String topic,MessageBody messageBody,int QOS)
+    {
+        mqttProducer.sendToMqtt(topic, QOS, JSON.toJSONString(messageBody));
+    }
+
+
+
+}

+ 28 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/mqtt/MqttOutListener.java

@@ -0,0 +1,28 @@
+package com.zd.alg.forward.serivce.mqtt;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Author: zhoupan
+ * @Date: 2021/11/15/19:01
+ * @Description:
+ */
+@Component
+@ConditionalOnProperty(prefix = "sys.mqtt" ,name = "inbound",havingValue = "true",matchIfMissing = true)
+public class MqttOutListener {
+    Logger logger = LoggerFactory.getLogger(MqttOutListener.class);
+
+    @Bean
+    @ServiceActivator(inputChannel = "mqttInputChannel")
+    public MessageHandler handler() {
+
+        return message -> logger.info("收到消息:"+ message.getPayload());
+    }
+
+}

+ 39 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/serivce/mqtt/MqttProducer.java

@@ -0,0 +1,39 @@
+package com.zd.alg.forward.serivce.mqtt;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.integration.annotation.MessagingGateway;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * 消息发送者
+ *
+ * @author zpp
+ */
+@Component
+@ConditionalOnProperty(prefix = "sys.mqtt" ,name = "outbound",havingValue = "true",matchIfMissing = true)
+@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
+public interface MqttProducer {
+
+    /**
+     * 发送信息
+     * @param data
+     */
+    void sendToMqtt(String data);
+
+    /**
+     * 指定主题发送信息
+     * @param topic
+     * @param payload
+     */
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
+
+    /**
+     * 指定主题和qos发送信息
+     * @param topic
+     * @param qos
+     * @param payload
+     */
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
+}

+ 78 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/thread/ThreadPoolTaskConfig.java

@@ -0,0 +1,78 @@
+package com.zd.alg.forward.thread;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.*;
+
+/**
+ * @author Administrator
+ */
+@Configuration
+@Slf4j
+public class ThreadPoolTaskConfig {
+
+    private static final int CORE_POOL_SIZE = 10;
+    private static final int MAX_POOL_SIZE = 100;
+    private static final int KEEP_ALIVE_TIME = 10;
+    private static final int QUEUE_CAPACITY = 200;
+    private static final String THREAD_NAME_PREFIX = "Async-Service-";
+
+    @Bean("taskExecutor")
+    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(CORE_POOL_SIZE);
+        executor.setMaxPoolSize(MAX_POOL_SIZE);
+        executor.setQueueCapacity(QUEUE_CAPACITY);
+        executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
+        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
+
+        // 线程池对拒绝任务的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        // 初始化
+        executor.initialize();
+        return executor;
+    }
+
+    /**
+     * 执行定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    public ScheduledExecutorService scheduledExecutorService() {
+        return new ScheduledThreadPoolExecutor(CORE_POOL_SIZE,
+                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t) {
+                super.afterExecute(r, t);
+                printException(r, t);
+            }
+        };
+    }
+
+
+    /**
+     * 打印线程异常信息
+     */
+    public static void printException(Runnable r, Throwable t) {
+        if (t == null && r instanceof Future<?>) {
+            try {
+                Future<?> future = (Future<?>) r;
+                if (future.isDone()) {
+                    future.get();
+                }
+            } catch (CancellationException ce) {
+                t = ce;
+            } catch (ExecutionException ee) {
+                t = ee.getCause();
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null) {
+            log.error(t.getMessage(), t);
+        }
+    }
+}

+ 93 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/util/Base64DecodedMultipartFile.java

@@ -0,0 +1,93 @@
+package com.zd.alg.forward.util;
+
+import org.springframework.web.multipart.MultipartFile;
+import sun.misc.BASE64Decoder;
+
+import java.io.*;
+
+public class Base64DecodedMultipartFile implements MultipartFile {
+    private final byte[] imgContent;
+    private final String header;
+
+    private Base64DecodedMultipartFile(byte[] imgContent, String header) {
+        this.imgContent = imgContent;
+        this.header = header.split(";")[0];
+    }
+
+    @Override
+    public String getName() {
+        return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
+    }
+
+    @Override
+    public String getOriginalFilename() {
+        return header;
+    }
+
+    @Override
+    public String getContentType() {
+        String splitComm[]=header.split(":");
+        if(splitComm.length>1){
+            return header.split(":")[1];
+        }
+        return header.split(":")[0];
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return imgContent == null || imgContent.length == 0;
+    }
+
+    @Override
+    public long getSize() {
+        return imgContent.length;
+    }
+
+    @Override
+    public byte[] getBytes() throws IOException {
+        return imgContent;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(imgContent);
+    }
+
+    @Override
+    public void transferTo(File dest) throws IOException, IllegalStateException {
+        new FileOutputStream(dest).write(imgContent);
+    }
+
+    /**
+     * base64转MultipartFile文件
+     *
+     * @param base64
+     * @return org.springframework.web.multipart.MultipartFile
+     * @author xianghl
+     * @date 2021/4/25/025 16:33
+     */
+    public static MultipartFile base64ToMultipart(String base64) {
+        try {
+            String[] baseStrs = base64.split(",");
+            BASE64Decoder decoder = new BASE64Decoder();
+            byte[] b;
+            if (baseStrs.length > 1) {
+                b = decoder.decodeBuffer(baseStrs[1]);
+            } else {
+                b = decoder.decodeBuffer(baseStrs[0]);
+            }
+            for (int i = 0; i < b.length; ++i) {
+                if (b[i] < 0) {
+                    b[i] += 256;
+                }
+            }
+            if (baseStrs.length > 1) {
+                return new Base64DecodedMultipartFile(b, "data:image/jpeg;base64");
+            }
+            return new Base64DecodedMultipartFile(b, baseStrs[0]);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}

+ 76 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/util/Base64ToMultipartFile.java

@@ -0,0 +1,76 @@
+package com.zd.alg.forward.util;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+public class Base64ToMultipartFile implements MultipartFile{
+    private final byte[] fileContent;
+
+    private final String extension;
+    private final String contentType;
+
+
+    /**
+     * @param base64
+     * @param dataUri     格式类似于: data:image/png;base64
+     */
+    public Base64ToMultipartFile(String base64, String dataUri) {
+        this.fileContent = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8));
+        this.extension = dataUri.split(";")[0].split("/")[1];
+        this.contentType = dataUri.split(";")[0].split(":")[1];
+    }
+
+    public Base64ToMultipartFile(String base64, String extension, String contentType) {
+        this.fileContent = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8));
+        this.extension = extension;
+        this.contentType = contentType;
+    }
+
+    @Override
+    public String getName() {
+        return "param_" + System.currentTimeMillis();
+    }
+
+    @Override
+    public String getOriginalFilename() {
+        return "file_" + System.currentTimeMillis() + "." + extension;
+    }
+
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return fileContent == null || fileContent.length == 0;
+    }
+
+    @Override
+    public long getSize() {
+        return fileContent.length;
+    }
+
+    @Override
+    public byte[] getBytes() throws IOException {
+        return fileContent;
+    }
+
+    @Override
+    public ByteArrayInputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(fileContent);
+    }
+
+    @Override
+    public void transferTo(File file) throws IOException, IllegalStateException {
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(fileContent);
+        }
+    }
+}

+ 201 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/util/FileUploadUtils.java

@@ -0,0 +1,201 @@
+package com.zd.alg.forward.util;
+
+import com.zd.common.core.exception.ServiceException;
+import com.zd.common.core.exception.file.FileNameLengthLimitExceededException;
+import com.zd.common.core.exception.file.FileSizeLimitExceededException;
+import com.zd.common.core.exception.file.InvalidExtensionException;
+import com.zd.common.core.utils.DateUtils;
+import com.zd.common.core.utils.IdUtils;
+import com.zd.common.core.utils.StringUtils;
+import com.zd.common.core.utils.file.MimeTypeUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 文件上传工具类
+ *
+ * @author zd
+ */
+public class FileUploadUtils {
+    /**
+     * 默认大小 50M
+     */
+    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+
+    /**
+     * 默认的文件名最大长度 100
+     */
+    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file    上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String upload(String baseDir, MultipartFile file) throws IOException {
+        try {
+            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        } catch (Exception e) {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir          相对应用的基目录
+     * @param file             上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException       如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException                          比如读写文件出错时
+     * @throws InvalidExtensionException            文件校验异常
+     */
+    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException {
+        int fileNamelength = file.getOriginalFilename().length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
+//            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+            throw new ServiceException("图片名字太长,请修改名字后,从新上传!");
+        }
+
+        assertAllowed(file, allowedExtension);
+
+        String fileName = extractFilename(file);
+
+        File desc = getAbsoluteFile(baseDir, fileName);
+        file.transferTo(desc);
+        String pathFileName = getPathFileName(fileName);
+        return pathFileName;
+    }
+//
+//    /**
+//     * 文件上传 -不修改文件名
+//     *
+//     * @param baseDir          相对应用的基目录
+//     * @param file             上传的文件
+//     * @param allowedExtension 上传文件类型
+//     * @return 返回上传成功的文件名
+//     * @throws FileSizeLimitExceededException       如果超出最大大小
+//     * @throws FileNameLengthLimitExceededException 文件名太长
+//     * @throws IOException                          比如读写文件出错时
+//     * @throws InvalidExtensionException            文件校验异常
+//     */
+//    public static final String uploadNoUpdateName(String baseDir, MultipartFile file, String[] allowedExtension)
+//            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+//            InvalidExtensionException {
+//        int fileNamelength = file.getOriginalFilename().length();
+//        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
+//            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+//        }
+//
+//        assertAllowed(file, allowedExtension);
+//
+////        String fileName = extractFilename(file);
+//
+//        String originalFilename = file.getOriginalFilename();
+//        File desc = getAbsoluteFile(baseDir, originalFilename);
+//        file.transferTo(desc);
+//        String pathFileName = getPathFileName(originalFilename);
+//        return pathFileName;
+//    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file) {
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
+        return fileName;
+    }
+
+    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException {
+        File desc = new File(uploadDir + File.separator + fileName);
+
+        if (!desc.exists()) {
+            if (!desc.getParentFile().exists()) {
+                desc.getParentFile().mkdirs();
+            }
+        }
+        return desc.isAbsolute() ? desc : desc.getAbsoluteFile();
+    }
+
+    private static final String getPathFileName(String fileName) throws IOException {
+        String pathFileName = "/" + fileName;
+        return pathFileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException      文件校验异常
+     */
+    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, InvalidExtensionException {
+        long size = file.getSize();
+        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE) {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {
+                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
+                        fileName);
+            } else {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+    }
+
+    /**
+     * 判断MIME类型是否是允许的MIME类型
+     *
+     * @param extension        上传文件类型
+     * @param allowedExtension 允许上传文件类型
+     * @return true/false
+     */
+    public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {
+        for (String str : allowedExtension) {
+            if (str.equalsIgnoreCase(extension)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param file 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtension(MultipartFile file) {
+        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+        if (StringUtils.isEmpty(extension)) {
+            extension = MimeTypeUtils.getExtension(file.getContentType());
+        }
+        return extension;
+    }
+}

+ 93 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/Base64DecodedMultipartFile.java

@@ -0,0 +1,93 @@
+package com.zd.alg.forward.utils;
+
+import org.springframework.web.multipart.MultipartFile;
+import sun.misc.BASE64Decoder;
+
+import java.io.*;
+
+public class Base64DecodedMultipartFile implements MultipartFile {
+    private final byte[] imgContent;
+    private final String header;
+
+    private Base64DecodedMultipartFile(byte[] imgContent, String header) {
+        this.imgContent = imgContent;
+        this.header = header.split(";")[0];
+    }
+
+    @Override
+    public String getName() {
+        return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
+    }
+
+    @Override
+    public String getOriginalFilename() {
+        return header;
+    }
+
+    @Override
+    public String getContentType() {
+        String splitComm[]=header.split(":");
+        if(splitComm.length>1){
+            return header.split(":")[1];
+        }
+        return header.split(":")[0];
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return imgContent == null || imgContent.length == 0;
+    }
+
+    @Override
+    public long getSize() {
+        return imgContent.length;
+    }
+
+    @Override
+    public byte[] getBytes() throws IOException {
+        return imgContent;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(imgContent);
+    }
+
+    @Override
+    public void transferTo(File dest) throws IOException, IllegalStateException {
+        new FileOutputStream(dest).write(imgContent);
+    }
+
+    /**
+     * base64转MultipartFile文件
+     *
+     * @param base64
+     * @return org.springframework.web.multipart.MultipartFile
+     * @author xianghl
+     * @date 2021/4/25/025 16:33
+     */
+    public static MultipartFile base64ToMultipart(String base64) {
+        try {
+            String[] baseStrs = base64.split(",");
+            BASE64Decoder decoder = new BASE64Decoder();
+            byte[] b;
+            if (baseStrs.length > 1) {
+                b = decoder.decodeBuffer(baseStrs[1]);
+            } else {
+                b = decoder.decodeBuffer(baseStrs[0]);
+            }
+            for (int i = 0; i < b.length; ++i) {
+                if (b[i] < 0) {
+                    b[i] += 256;
+                }
+            }
+            if (baseStrs.length > 1) {
+                return new Base64DecodedMultipartFile(b, "data:image/jpeg;base64");
+            }
+            return new Base64DecodedMultipartFile(b, baseStrs[0]);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}

+ 76 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/Base64ToMultipartFile.java

@@ -0,0 +1,76 @@
+package com.zd.alg.forward.utils;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+public class Base64ToMultipartFile implements MultipartFile{
+    private final byte[] fileContent;
+
+    private final String extension;
+    private final String contentType;
+
+
+    /**
+     * @param base64
+     * @param dataUri     格式类似于: data:image/png;base64
+     */
+    public Base64ToMultipartFile(String base64, String dataUri) {
+        this.fileContent = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8));
+        this.extension = dataUri.split(";")[0].split("/")[1];
+        this.contentType = dataUri.split(";")[0].split(":")[1];
+    }
+
+    public Base64ToMultipartFile(String base64, String extension, String contentType) {
+        this.fileContent = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8));
+        this.extension = extension;
+        this.contentType = contentType;
+    }
+
+    @Override
+    public String getName() {
+        return "param_" + System.currentTimeMillis();
+    }
+
+    @Override
+    public String getOriginalFilename() {
+        return "file_" + System.currentTimeMillis() + "." + extension;
+    }
+
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return fileContent == null || fileContent.length == 0;
+    }
+
+    @Override
+    public long getSize() {
+        return fileContent.length;
+    }
+
+    @Override
+    public byte[] getBytes() throws IOException {
+        return fileContent;
+    }
+
+    @Override
+    public ByteArrayInputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(fileContent);
+    }
+
+    @Override
+    public void transferTo(File file) throws IOException, IllegalStateException {
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(fileContent);
+        }
+    }
+}

+ 201 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/FileUploadUtils.java

@@ -0,0 +1,201 @@
+package com.zd.alg.forward.utils;
+
+import com.zd.common.core.exception.ServiceException;
+import com.zd.common.core.exception.file.FileNameLengthLimitExceededException;
+import com.zd.common.core.exception.file.FileSizeLimitExceededException;
+import com.zd.common.core.exception.file.InvalidExtensionException;
+import com.zd.common.core.utils.DateUtils;
+import com.zd.common.core.utils.IdUtils;
+import com.zd.common.core.utils.StringUtils;
+import com.zd.common.core.utils.file.MimeTypeUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 文件上传工具类
+ *
+ * @author zd
+ */
+public class FileUploadUtils {
+    /**
+     * 默认大小 50M
+     */
+    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+
+    /**
+     * 默认的文件名最大长度 100
+     */
+    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file    上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String upload(String baseDir, MultipartFile file) throws IOException {
+        try {
+            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        } catch (Exception e) {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir          相对应用的基目录
+     * @param file             上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException       如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException                          比如读写文件出错时
+     * @throws InvalidExtensionException            文件校验异常
+     */
+    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException {
+        int fileNamelength = file.getOriginalFilename().length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
+//            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+            throw new ServiceException("图片名字太长,请修改名字后,从新上传!");
+        }
+
+        assertAllowed(file, allowedExtension);
+
+        String fileName = extractFilename(file);
+
+        File desc = getAbsoluteFile(baseDir, fileName);
+        file.transferTo(desc);
+        String pathFileName = getPathFileName(fileName);
+        return pathFileName;
+    }
+//
+//    /**
+//     * 文件上传 -不修改文件名
+//     *
+//     * @param baseDir          相对应用的基目录
+//     * @param file             上传的文件
+//     * @param allowedExtension 上传文件类型
+//     * @return 返回上传成功的文件名
+//     * @throws FileSizeLimitExceededException       如果超出最大大小
+//     * @throws FileNameLengthLimitExceededException 文件名太长
+//     * @throws IOException                          比如读写文件出错时
+//     * @throws InvalidExtensionException            文件校验异常
+//     */
+//    public static final String uploadNoUpdateName(String baseDir, MultipartFile file, String[] allowedExtension)
+//            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+//            InvalidExtensionException {
+//        int fileNamelength = file.getOriginalFilename().length();
+//        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
+//            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+//        }
+//
+//        assertAllowed(file, allowedExtension);
+//
+////        String fileName = extractFilename(file);
+//
+//        String originalFilename = file.getOriginalFilename();
+//        File desc = getAbsoluteFile(baseDir, originalFilename);
+//        file.transferTo(desc);
+//        String pathFileName = getPathFileName(originalFilename);
+//        return pathFileName;
+//    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file) {
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
+        return fileName;
+    }
+
+    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException {
+        File desc = new File(uploadDir + File.separator + fileName);
+
+        if (!desc.exists()) {
+            if (!desc.getParentFile().exists()) {
+                desc.getParentFile().mkdirs();
+            }
+        }
+        return desc.isAbsolute() ? desc : desc.getAbsoluteFile();
+    }
+
+    private static final String getPathFileName(String fileName) throws IOException {
+        String pathFileName = "/" + fileName;
+        return pathFileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException      文件校验异常
+     */
+    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, InvalidExtensionException {
+        long size = file.getSize();
+        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE) {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {
+                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
+                        fileName);
+            } else {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+    }
+
+    /**
+     * 判断MIME类型是否是允许的MIME类型
+     *
+     * @param extension        上传文件类型
+     * @param allowedExtension 允许上传文件类型
+     * @return true/false
+     */
+    public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {
+        for (String str : allowedExtension) {
+            if (str.equalsIgnoreCase(extension)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param file 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtension(MultipartFile file) {
+        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+        if (StringUtils.isEmpty(extension)) {
+            extension = MimeTypeUtils.getExtension(file.getContentType());
+        }
+        return extension;
+    }
+}

+ 113 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/HttpUtils.java

@@ -0,0 +1,113 @@
+package com.zd.alg.forward.utils;
+
+import com.zd.alg.forward.config.AlgorithmYml;
+import com.zd.alg.forward.domain.DataPostAnalysisRespDto;
+import com.zd.alg.forward.domain.ImgPostResponse;
+import com.zd.alg.forward.properties.FireProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.*;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+/**
+ * @author Administrator
+ */
+@Slf4j
+public class HttpUtils {
+
+    private HttpUtils() {
+        throw new IllegalStateException("HttpUtils class");
+    }
+
+    /**
+     * 构造算法文件逆流
+     */
+    public static HttpEntity<MultiValueMap<String, Object>> getHttpEntityMap(File file, MultiValueMap<String, Object> params) {
+        //设置请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+        //文件转为文件系统资源
+        return getHttpEntity(file, params, headers);
+    }
+
+    private static HttpEntity<MultiValueMap<String, Object>> getHttpEntity(File file, MultiValueMap<String, Object> params, HttpHeaders headers) {
+        //文件转为文件系统资源
+        FileSystemResource fileSystemResource = new FileSystemResource(file);
+        //设置请求体,注意是LinkedMultiValueMap
+        MultiValueMap<String, Object> form = getStringObjectMultiValueMap(fileSystemResource);
+        form.addAll(params);
+        //用HttpEntity封装整个请求报文
+        return new HttpEntity<>(form, headers);
+    }
+
+    public static File multipartFileToFile(MultipartFile file) throws IOException {
+        String originalFilename = file.getOriginalFilename() == null ? "" : file.getOriginalFilename();
+        assert originalFilename!=null;
+        String[] filename = originalFilename.split("\\.");
+        File toFile = File.createTempFile(filename[0], "." + filename[1]);
+        file.transferTo(toFile);
+        log.info("================>{}",toFile.getAbsoluteFile());
+        //toFile.deleteOnExit();//在jvm 退出时删除
+        return toFile;
+    }
+
+    /**
+     * 构建算法图片post请求 参数:只构建文件
+     */
+    public static MultiValueMap<String, Object> getStringObjectMultiValueMap(FileSystemResource fileSystemResource) {
+        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
+        form.add("file", fileSystemResource);
+        return form;
+    }
+
+    private static MultiValueMap<String, Object> getStringObjectMultiValueMap(String extension) {
+        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
+        //1-同步(默认),0-异步
+        form.add("sync", 1);
+        form.add("timeStamp", LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
+        form.add("extension", extension);
+        return form;
+    }
+
+    /**
+     * 构建算法图片post请求 参数
+     */
+    public static MultiValueMap<String, Object> getStringObjectMultiValueMap(AlgorithmYml.CheckValid checkValid, String extension) {
+        MultiValueMap<String, Object> form = getStringObjectMultiValueMap(extension);
+        form.add("algoId", checkValid.getAlgoId());
+        form.add("did", checkValid.getDid());
+        return form;
+    }
+
+    /**
+     * 构建算法图片post请求 参数
+     */
+    public static MultiValueMap<String, Object> getMultiValueMap(FireProperties properties, String extension) {
+        MultiValueMap<String, Object> form = getStringObjectMultiValueMap(extension);
+        form.add("algoId", properties.getAlgoId());
+        form.add("did", properties.getDid());
+        return form;
+    }
+
+    public static ImgPostResponse<DataPostAnalysisRespDto> send(RestTemplate restTemplate, HttpEntity<MultiValueMap<String, Object>> files, AlgorithmYml algorithmYml) {
+        ParameterizedTypeReference<ImgPostResponse<DataPostAnalysisRespDto>> reference = new ParameterizedTypeReference<ImgPostResponse<DataPostAnalysisRespDto>>() {
+        };
+        ResponseEntity<ImgPostResponse<DataPostAnalysisRespDto>> response = restTemplate.exchange(algorithmYml.getImgPostUrl(), HttpMethod.POST, files, reference);
+        if (response.getStatusCode() != HttpStatus.OK) {
+            log.error("算法服务请求异常,请查看算服务器");
+        }
+        if (response.getBody() == null) {
+            log.error("算法服务接口返回异常");
+        }
+        return response.getBody();
+    }
+}

+ 51 - 0
zd-modules/zd-algorithm/src/main/java/com/zd/alg/forward/utils/VideoUtils.java

@@ -0,0 +1,51 @@
+package com.zd.alg.forward.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.bytedeco.javacv.Java2DFrameConverter;
+
+import java.awt.image.BufferedImage;
+
+/**
+ * @author Administrator
+ */
+@Slf4j
+public class VideoUtils {
+
+    private VideoUtils() {
+        throw new IllegalStateException("VideoUtils class");
+    }
+    /**
+     * 帧率
+     */
+    private static final int FRAME_RATE = 25;
+
+    /**
+     * 视频宽度
+     */
+    private static final int FRAME_WIDTH = 1920;
+    /**
+     * 视频高度
+     */
+    private static final int FRAME_HEIGHT = 1080;
+
+    public static FFmpegFrameGrabber createGrabber(String rtsp) throws FFmpegFrameGrabber.Exception {
+        FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(rtsp);
+        grabber.setOption("rtsp_transport", "tcp");
+        grabber.setImageHeight(FRAME_HEIGHT);
+        grabber.setImageWidth(FRAME_WIDTH);
+        grabber.setFrameRate(FRAME_RATE);
+        return grabber;
+    }
+
+    /**
+     * frame 转图片流
+     */
+    public static BufferedImage frameToBufferedImage(Frame frame) {
+        //创建BufferedImage对象
+        try (Java2DFrameConverter converter = new Java2DFrameConverter()) {
+            return converter.getBufferedImage(frame);
+        }
+    }
+}

+ 77 - 3
zd-modules/zd-algorithm/src/main/resources/application.yml

@@ -37,8 +37,10 @@ spring:
 speaker:
   port: 8888
   isOnline: 0  #1表示公网部署,0表示局域网部署
-
-#人脸识别参数
+# 电话报警
+alarm:
+  host: http://${ALARM_HOST:192.168.1.100}
+  retry: 3
 face:
   appId: A3XKg9AUQhgd7RjBk8JKDrVkkoQRd6W9kxm5R2MtAeu1
   ## windows:HXf4r2UBofUkGPUPQPQKag58fQoTiw5LHBMHPeFbY1n8  linux:HXf4r2UBofUkGPUPQPQKag58XJEK8LYpNvi1qH4g1t4f
@@ -46,4 +48,76 @@ face:
   linuxKey: HXf4r2UBofUkGPUPQPQKag58XJEK8LYpNvi1qH4g1t4f
   winKey: HXf4r2UBofUkGPUPQPQKag58fQoTiw5LHBMHPeFbY1n8
   activation: true
-  init: true
+  init: true
+sys:
+  mqtt:
+    topics: lab/news  # 订阅主题, 多个用,隔开
+    ## 默认为true 不需要订阅则关闭
+    inbound: true
+    ## 默认为true 不需要发送则关闭
+    outbound: true
+  config:
+    loginUri: http://${INNER_HOST:192.168.1.88}/${INNER_PORT:8080}/
+    #跳过阈值 跳过检查项触发阈值
+    jumpThreshold: 2
+    ## 日志检查提交地址
+    checkLogUrl: laboratory/checklog
+    ## 图片提交地址
+    # 原地址 http://180.76.134.43:31005/api/analysis/image
+    imgPostUrl: http://${ARITHMETIC_HOST:192.168.1.248}:${ARITHMETIC_PORT:80}/api/open/data/postAnalysis
+    #loginUri: 默认为云上地址 http://lab.sxitdlc.com/labSystem/
+    #转发目标地址
+    targetUrl: https://${NET_HOST:lab.sxitdlc.com}/appTest/
+    #1号喇叭IP
+    loudspeakerIp1: 192.168.1.103
+    # 2号喇叭IP
+    loudspeakerIp2:
+    # 语音播报开关
+    loudspeakerSwitch: true
+    #临时媒体文件存放地址
+    imgTemp: /home/upload
+    algorithmMap:
+      ## 护目镜
+      1:
+        algoId: 9787
+        did: 1557192675937570818
+        algorithmName: "护目镜识别图片算法"
+      ## 实验服
+      2:
+        algoId: 10285
+        did: 1557192675937570818
+        algorithmName: "实验服识别图片算法"
+    videoAidMap:
+      9688:
+        tips: 非法闯入人员
+        name: peopleCheckResultValidImpl
+      10258:
+        tips: 未穿戴实验服人员
+      9787:
+        tips: 未佩戴护目镜人员
+    alarmConfigMap:
+      9610:
+        funNum: "huoyan"
+        describe: "火焰识别"
+      9611:
+        funNum: "huoyan"
+        describe: "火焰识别"
+      9684:
+        funNum: "yanwu"
+        describe: "烟雾识别"
+      9688:
+        funNum: "juzhong"
+        describe: "聚众识别"
+      9690:
+        funNum: "ligang"
+        describe: "离岗识别"
+
+file:
+  path: /home/AIPIC
+fire:
+  algo_id: 9611
+  did: 1557192675937570818
+  algorithm_name: "火焰算法"
+  hardware_num: "040C7E75CA85"
+  stream_url: "rtsp://admin:hk123456@192.168.1.64:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1"
+  wait_time: 5