Ver código fonte

火焰图片算法实现

ty130316261 3 anos atrás
pai
commit
3e3a32a03c

+ 12 - 0
zd-modules/zd-forward/pom.xml

@@ -83,6 +83,18 @@
             <groupId>com.zd</groupId>
             <artifactId>zd-common-mqtt</artifactId>
         </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>
 
     </dependencies>
     <build>

+ 2 - 6
zd-modules/zd-forward/src/main/java/com/zd/forward/ForwardApp.java

@@ -2,6 +2,7 @@ package com.zd.forward;
 
 import com.zd.common.security.annotation.EnableCustomConfig;
 import com.zd.common.security.annotation.EnableRyFeignClients;
+import com.zd.forward.properties.FireProperties;
 import com.zd.forward.serivce.LoginService;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -11,15 +12,10 @@ import org.springframework.context.ConfigurableApplicationContext;
 import springfox.documentation.oas.annotations.EnableOpenApi;
 import zd.common.mqtt.config.MqttProperties;
 
-/**
- * @Author: zhoupan
- * @Date: 2021/11/13/11:41
- * @Description:
- */
 @EnableCustomConfig
 @EnableRyFeignClients
 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
-@EnableConfigurationProperties(MqttProperties.class)
+@EnableConfigurationProperties({MqttProperties.class, FireProperties.class})
 @EnableOpenApi
 public class ForwardApp {
 

+ 0 - 6
zd-modules/zd-forward/src/main/java/com/zd/forward/component/AppStartedListener.java

@@ -17,11 +17,6 @@ import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 
-/**
- * @Author: zhoupan
- * @Date: 2021/11/14/11:30
- * @Description:
- */
 @Component
 public class AppStartedListener implements ApplicationListener<ApplicationStartedEvent> {
     Logger logger = LoggerFactory.getLogger(AppStartedListener.class);
@@ -106,6 +101,5 @@ public class AppStartedListener implements ApplicationListener<ApplicationStarte
                 applicationStartedEvent.getApplicationContext().close();
             }
         });
-
     }
 }

+ 8 - 1
zd-modules/zd-forward/src/main/java/com/zd/forward/config/AlgorithmYml.java

@@ -1,6 +1,7 @@
 package com.zd.forward.config;
 
 
+import com.zd.common.core.exception.ServiceException;
 import com.zd.forward.serivce.CheckResultValid;
 import com.zd.forward.serivce.ImageCheckResultValidImpl;
 import com.zd.forward.serivce.VideoCheckResultValid;
@@ -12,6 +13,7 @@ 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
@@ -216,7 +218,7 @@ public class AlgorithmYml {
         @NotNull (message = "算法ID不能为空")
         private Integer algoId;
         //cid
-        @NotNull (message = "=数据ID不能为空")
+        @NotNull (message = "数据ID不能为空")
         private String did;
 
         private String algorithmName;
@@ -283,4 +285,9 @@ public class AlgorithmYml {
     public void setAlgorithmMap(Map<Integer,CheckValid> algorithmMap) {
         this.algorithmMap = algorithmMap;
     }
+
+    public AlgorithmYml.CheckValid getCheckValid(Integer code) {
+        return Optional.ofNullable(getAlgorithmMap().get(code))
+                .orElseThrow(() -> new ServiceException("未配置对应算法code:" + code));
+    }
 }

+ 34 - 0
zd-modules/zd-forward/src/main/java/com/zd/forward/listener/StartListener.java

@@ -0,0 +1,34 @@
+package com.zd.forward.listener;
+
+import com.zd.forward.serivce.FireImageService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 监听Spring容器启动完成,完成后启动Netty服务器
+ *
+ * @author dgs
+ **/
+@Component
+@Slf4j
+public class StartListener implements ApplicationRunner {
+    @Resource
+    private FireImageService fireImageService;
+
+    @Override
+    public void run(ApplicationArguments args) {
+        //未验证部分,待验证后启用
+//        new Thread(new Runnable() {
+//            @Override
+//            public void run() {
+//                synchronized (this){
+//                    fireImageService.catchImage();
+//                }
+//            }
+//        }).start();
+    }
+}

+ 46 - 0
zd-modules/zd-forward/src/main/java/com/zd/forward/properties/FireProperties.java

@@ -0,0 +1,46 @@
+package com.zd.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;
+
+@ConfigurationProperties("fire")
+@RefreshScope
+@Validated
+@Data
+public class FireProperties {
+
+    @NotNull(message = "算法ID不能为空")
+    private Integer algoId;
+    //cid
+    @NotNull (message = "=数据ID不能为空")
+    private String did;
+
+    /**
+     * 实验室ID,和设备编号二选一即可
+     */
+    private Integer labId;
+
+    /**
+     * 设备编号,和实验室ID二选一即可
+     */
+    private String hardwareNum;
+    /**
+     * 算法名称
+     */
+    private String algorithmName;
+
+    /**
+     * 视频流地址
+     */
+    private String streamURL;
+
+    /**
+     * 间隔时间
+     */
+    private Integer waitTime=1;
+
+}

+ 8 - 120
zd-modules/zd-forward/src/main/java/com/zd/forward/serivce/CheckService.java

@@ -20,6 +20,7 @@ import com.zd.forward.domain.VideoRequestData;
 import com.zd.forward.serivce.mqtt.CommonSend;
 import com.zd.forward.util.Base64DecodedMultipartFile;
 import com.zd.forward.util.FileUploadUtils;
+import com.zd.forward.util.HttpUtils;
 import com.zd.system.api.RemoteFileService;
 import com.zd.system.api.alarm.RemoteAlarmService;
 import com.zd.system.api.alarm.domain.AlarmEntrty;
@@ -54,6 +55,8 @@ import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.*;
 
+import static com.zd.forward.util.HttpUtils.*;
+
 @Service
 @EnableConfigurationProperties(AlgorithmYml.class)
 @RefreshScope
@@ -125,10 +128,10 @@ public class CheckService {
             String[] checkItem = labCheckIn.split(",");
             for (String code : checkItem) {
                 //========= 获取算法INFO ===========
-                AlgorithmYml.CheckValid checkValid = getCheckValid(Integer.valueOf(code));
+                AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(code));
                 MultiValueMap<String, Object> params = getStringObjectMultiValueMap(checkValid, String.valueOf(id));
                 HttpEntity<MultiValueMap<String, Object>> files=getHttpEntityMap(file,params);
-                ImgPostResponse<DataPostAnalysisRespDto> send = send(files, checkValid, id);
+                ImgPostResponse<DataPostAnalysisRespDto> send = HttpUtils.send(restTemplateLocal,files,algorithmYml);
                 if (send == null || send.getStatus_code() != 1000) {
                     assert send != null;
                     logger.error(send.getMessage());
@@ -175,10 +178,7 @@ public class CheckService {
     }
 
     /**
-     * @param code
-     * @param file
      * @param id   进出记录ID
-     * @return
      */
     public R checkAndCommit(String code, MultipartFile file, Long id) {
         //========= 请求超时验证部分开始 ===========
@@ -187,7 +187,7 @@ public class CheckService {
         if (fail.getCode() != 200) return fail;
         //========= 请求超时验证部分结束 ===========
         //========= 获取算法INFO ===========
-        AlgorithmYml.CheckValid checkValid = getCheckValid(Integer.valueOf(code));
+        AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(code));
         //=========发送验证信息到算法服务开始
         JSONObject send = null;
         try {
@@ -270,7 +270,7 @@ public class CheckService {
         String[] codesArrs = codes.split(",");
         for (int i = 0; i < codesArrs.length; i++) {
             R r = checkAndCommit(codesArrs[i], file[i], ids[i]);
-            AlgorithmYml.CheckValid checkValid = getCheckValid(Integer.valueOf(codesArrs[i]));
+            AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(codesArrs[i]));
             result.put(checkValid.getAlgoId(), r.getMsg() == null ? "通过" : r.getMsg());
 
         }
@@ -407,40 +407,6 @@ public class CheckService {
         sendSginAccessLogService.sendAddLogRest(code, id, f, msg, token, algorithmYml.getLoginUri() + algorithmYml.getCheckLogUrl());
     }
 
-    private AlgorithmYml.CheckValid getCheckValid(Integer code) {
-        return Optional.ofNullable(algorithmYml.getAlgorithmMap().get(code))
-                .orElseThrow(() -> new ServiceException("未配置对应算法code:" + code));
-    }
-
-    /**
-     * 构造算法文件逆流
-     * @param file
-     * @param params
-     * @return
-     */
-    private HttpEntity<MultiValueMap<String, Object>> getHttpEntityMap(MultipartFile file, MultiValueMap<String, Object> params){
-        try {
-            //设置请求头
-            HttpHeaders headers = new HttpHeaders();
-            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
-            // 文件本地存储收集
-            //FileUploadUtils.upload(localFilePath, file);
-            //MultipartFile 转为临时文件
-            File uploadFile = multipartFileToFile(file);
-            //文件转为文件系统资源
-            FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
-            //设置请求体,注意是LinkedMultiValueMap
-            MultiValueMap<String, Object> form = getStringObjectMultiValueMap(fileSystemResource);
-            form.addAll(params);
-            //用HttpEntity封装整个请求报文
-            HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
-            return files;
-        }catch (IOException ex){
-            ex.printStackTrace();
-        }
-        return null;
-    }
-
     /**
      * 获取算法请求地址
      * @param checkValid
@@ -460,23 +426,6 @@ public class CheckService {
         return paramUrl;
     }
 
-
-    public ImgPostResponse<DataPostAnalysisRespDto> send(HttpEntity<MultiValueMap<String, Object>> files,AlgorithmYml.CheckValid checkValid, Long id){
-        ParameterizedTypeReference<ImgPostResponse<DataPostAnalysisRespDto>> reference = new ParameterizedTypeReference<ImgPostResponse<DataPostAnalysisRespDto>>() {
-        };
-        ResponseEntity<ImgPostResponse<DataPostAnalysisRespDto>> response = restTemplateLocal.exchange(algorithmYml.getImgPostUrl(), HttpMethod.POST, files, reference);
-        if (response.getStatusCode()!=HttpStatus.OK){
-            logger.error("算法服务请求异常,请查看算服务器");
-            throw new ServiceException("算法服务请求异常,请查看算服务器");
-        }
-        if (response.getBody()==null){
-            logger.error("算法服务接口返回异常");
-            throw new ServiceException("算法服务接口返回异常");
-        }
-        return response.getBody();
-    }
-
-
     /**
      * 图片算法请求参数
      *
@@ -500,13 +449,7 @@ public class CheckService {
             FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
             //设置请求体,注意是LinkedMultiValueMap
             MultiValueMap<String, Object> params = getStringObjectMultiValueMap(checkValid, String.valueOf(id));
-//            Set<String> keySet = params.keySet();
-//            StringBuilder stb = new StringBuilder();
-//            String paramStr = "";
-//            for (String key : keySet) {
-//                stb.append("&").append(key).append("=").append(params.get(key).get(0).toString());
-//            }
-//            paramStr = String.valueOf(stb).replaceFirst("&", "?");
+
             MultiValueMap<String, Object> form = getStringObjectMultiValueMap(fileSystemResource);
             params.addAll(form);
             //用HttpEntity封装整个请求报文
@@ -524,61 +467,6 @@ public class CheckService {
         }
     }
 
-
-    /**
-     * 构建算法图片post请求 参数:只构建文件
-     *
-     * @param
-     * @param fileSystemResource
-     * @return
-     */
-    private MultiValueMap<String, Object> getStringObjectMultiValueMap(FileSystemResource fileSystemResource) {
-        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
-        form.add("file", fileSystemResource);
-        return form;
-    }
-
-    /**
-     * 构建算法图片post请求 参数
-     *
-     * @param checkValid
-     * @return
-     */
-    private 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;
-    }
-
-    private MultiValueMap<String, Object> getStringObjectMultiValueMap() {
-
-        return getStringObjectMultiValueMap(true, null);
-    }
-
-    private MultiValueMap<String, Object> getStringObjectMultiValueMap(String extension) {
-
-        return getStringObjectMultiValueMap(true, extension);
-    }
-
-    private MultiValueMap<String, Object> getStringObjectMultiValueMap(boolean sync, String extension) {
-        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
-        //1-同步(默认),0-异步
-        form.add("sync", sync ? 1 : 0);
-        form.add("timestamp", LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
-        form.add("extension", extension);
-        return form;
-    }
-
-    public static File multipartFileToFile(MultipartFile file) throws IOException {
-        String originalFilename = file.getOriginalFilename();
-        String[] filename = originalFilename.split("\\.");
-        File toFile = File.createTempFile(filename[0], "." + filename[1]);
-        file.transferTo(toFile);
-        //toFile.deleteOnExit();//在jvm 退出时删除
-        return toFile;
-    }
-
     /**
      * base64转图片/视频
      *

+ 119 - 0
zd-modules/zd-forward/src/main/java/com/zd/forward/serivce/FireImageService.java

@@ -0,0 +1,119 @@
+package com.zd.forward.serivce;
+
+import com.zd.common.core.constant.Constants;
+import com.zd.common.core.domain.R;
+import com.zd.common.core.exception.ServiceException;
+import com.zd.common.core.utils.IdGen;
+import com.zd.common.core.utils.StringUtils;
+import com.zd.forward.config.AlgorithmYml;
+import com.zd.forward.domain.DataPostAnalysisRespDto;
+import com.zd.forward.domain.ImgPostResponse;
+import com.zd.forward.properties.FireProperties;
+import com.zd.forward.util.HttpUtils;
+import com.zd.forward.util.VideoUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.bytedeco.javacv.FrameGrabber;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static com.zd.forward.util.HttpUtils.getHttpEntityMap;
+import static com.zd.forward.util.HttpUtils.getStringObjectMultiValueMap;
+import static com.zd.forward.util.VideoUtil.FrameToBufferedImage;
+
+/**
+ * 火焰图片抓拍处理
+ */
+@Service
+@Slf4j
+public class FireImageService {
+
+    @Resource
+    private FireProperties fireProperties;
+    @Resource
+    private AlgorithmYml algorithmYml;
+
+    @Resource(name = "restTemplateLocal")
+    private RestTemplate restTemplateLocal;
+    @Resource
+    private SendSginAccessLogService sendSginAccessLogService;
+
+    /**
+     * 上传文件存储在本地的根路径
+     */
+    @Value("${file.path:/home/AIPIC}")
+    private String imagePath;
+
+    public void catchImage(){
+
+        String streamURL = fireProperties.getStreamURL();
+        if (streamURL==null){
+            throw new ServiceException("未配置流媒体地址");
+        }
+
+        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(streamURL);
+        try {
+            grabber.start();
+            //获取第一帧
+            Frame frame = grabber.grabFrame();
+            if (frame != null) {
+                int flag = 0;
+                while (frame != null) {
+                    //视频快照
+                    frame = grabber.grabImage();
+                    long uuid = IdGen.snowflakeId();
+                    //文件储存对象
+                    String fileName = imagePath+File.pathSeparator + uuid + "_" + flag;
+                    File tempFile = File.createTempFile(fileName, ".jpg");
+                    ImageIO.write(FrameToBufferedImage(frame), "jpg", tempFile);
+                    send(tempFile);
+                }
+                //停止
+                grabber.stop();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                grabber.stop();
+            } catch (FrameGrabber.Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    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=getHttpEntityMap(file,params);
+        ImgPostResponse<DataPostAnalysisRespDto> send = HttpUtils.send(restTemplateLocal,files,algorithmYml);
+        if (send == null || send.getStatus_code() != 1000) {
+            assert send != null;
+            log.error(send.getMessage());
+            throw new ServiceException(send.getMessage());
+        }
+        DataPostAnalysisRespDto data = send.getData();
+
+        Map<String,Object> result = (Map<String, Object>) Optional.ofNullable(data.getAnalysisDatas().get(0).getResult()).orElse(Collections.emptyMap());
+        Map<String,Object> algorithmData = (Map<String, Object>)  Optional.ofNullable(result.get("algorithm_data")).orElse(Collections.emptyMap());
+        boolean alert = algorithmData.getOrDefault("is_alert", "").toString().equals("false");
+        if (!alert){
+            sendSginAccessLogService.saveAlarm(fireProperties);
+        }
+    }
+}

+ 44 - 0
zd-modules/zd-forward/src/main/java/com/zd/forward/serivce/SendSginAccessLogService.java

@@ -8,6 +8,7 @@ import com.zd.common.core.utils.StringUtils;
 import com.zd.common.core.web.domain.AjaxResult;
 import com.zd.forward.config.AlgorithmYml;
 import com.zd.forward.domain.VideoRequestData;
+import com.zd.forward.properties.FireProperties;
 import com.zd.system.api.domain.Algorithm;
 import com.zd.system.api.domain.ParamVo;
 import com.zd.system.api.domain.PlayVo;
@@ -97,6 +98,24 @@ public class SendSginAccessLogService {
 
     }
 
+    public void saveAlarm(FireProperties properties) {
+        String hardwareNum = properties.getHardwareNum();
+        if (Boolean.TRUE.equals(redisTemplate.hasKey(hardwareNum))) {
+            Integer count = (Integer) redisTemplate.opsForValue().get(hardwareNum);
+            if (count == null) {
+                count = 0;
+            }
+            redisTemplate.opsForValue().set(hardwareNum, count + 1, 30, TimeUnit.SECONDS);
+            R<Object> r = send(properties);
+            if (r.getCode() != HttpStatus.SUCCESS) {
+                logger.error("火焰警报失败原因:{}", r.getMsg());
+            }
+        } else {
+            redisTemplate.opsForValue().set(hardwareNum, 1, 30, TimeUnit.SECONDS);
+        }
+
+    }
+
     public void playMp3() {
         if (!algorithmYml.isLoudspeakerSwitch()){
             return;
@@ -153,6 +172,31 @@ public class SendSginAccessLogService {
         return R.fail("未找到算法");
     }
 
+    /**
+     * 发送火焰警报
+     */
+    public R<Object> send(FireProperties properties) {
+        Map<String, Object> requestMap = new HashMap<>();
+        List<Map<String, Object>> maps = new ArrayList<>();
+        Integer aid = properties.getAlgoId();
+        Map<Integer, AlgorithmYml.AlarmConfig> alarmConfigMap = algorithmYml.getAlarmConfigMap();
+        if (alarmConfigMap.containsKey(aid)) {
+            AlgorithmYml.AlarmConfig alarmConfig = alarmConfigMap.get(aid);
+            Map<String, Object> params = new HashMap<>();
+            params.put("hardwareNum", properties.getHardwareNum());
+            params.put("subId", properties.getLabId());
+            params.put("val", 1);
+            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);
+        }
+        return R.fail("未找到算法");
+    }
+
     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);

+ 126 - 0
zd-modules/zd-forward/src/main/java/com/zd/forward/util/HttpUtils.java

@@ -0,0 +1,126 @@
+package com.zd.forward.util;
+
+import com.zd.common.core.exception.ServiceException;
+import com.zd.forward.config.AlgorithmYml;
+import com.zd.forward.domain.DataPostAnalysisRespDto;
+import com.zd.forward.domain.ImgPostResponse;
+import com.zd.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;
+
+@Slf4j
+public class HttpUtils {
+
+    /**
+     * 构造算法文件逆流
+     */
+    public static HttpEntity<MultiValueMap<String, Object>> getHttpEntityMap(MultipartFile file, MultiValueMap<String, Object> params){
+        try {
+            //设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+            // 文件本地存储收集
+            //FileUploadUtils.upload(localFilePath, file);
+            //MultipartFile 转为临时文件
+            File uploadFile = multipartFileToFile(file);
+            //文件转为文件系统资源
+            return getHttpEntity(uploadFile, params, headers);
+        }catch (IOException ex){
+            ex.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 构造算法文件逆流
+     */
+    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();
+        String[] filename = originalFilename.split("\\.");
+        File toFile = File.createTempFile(filename[0], "." + filename[1]);
+        file.transferTo(toFile);
+        //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("算法服务请求异常,请查看算服务器");
+            throw new ServiceException("算法服务请求异常,请查看算服务器");
+        }
+        if (response.getBody()==null){
+            log.error("算法服务接口返回异常");
+            throw new ServiceException("算法服务接口返回异常");
+        }
+        return response.getBody();
+    }
+}

+ 157 - 0
zd-modules/zd-forward/src/main/java/com/zd/forward/util/VideoUtil.java

@@ -0,0 +1,157 @@
+package com.zd.forward.util;
+
+import com.zd.common.core.utils.IdGen;
+import com.zd.common.redis.service.RedisService;
+import com.zd.forward.properties.FireProperties;
+import org.bytedeco.ffmpeg.global.avcodec;
+import org.bytedeco.javacv.*;
+import org.springframework.beans.factory.annotation.Value;
+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.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class VideoUtil {
+
+    //调用原子线程判断
+    private static final AtomicBoolean running = new AtomicBoolean(true);
+
+    public String streamURL;// 流地址
+
+    public String filePath;// 视频文件路径
+
+    /**
+     * 上传文件存储在本地的根路径
+     */
+    @Value("${file.path:/home/AIPIC}")
+    public String imagePath;// 图片路径,存放截取视频某一帧的图片
+
+    @Resource
+    RedisService redisService;
+    @Resource
+    FireProperties fireProperties;
+
+    public void setStreamURL(String streamURL) {
+        this.streamURL = streamURL;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public void setImagePath(String imagePath) {
+        this.imagePath = imagePath;
+    }
+
+    /**
+     * frame 转图片流
+     */
+    public static BufferedImage FrameToBufferedImage(Frame frame) {
+        //创建BufferedImage对象
+        Java2DFrameConverter converter = new Java2DFrameConverter();
+        return converter.getBufferedImage(frame);
+    }
+
+
+    /**
+     * 执行视频流抓取
+     */
+    public void run(Long uuid, boolean screenshot) {
+        Map<Long, Object> resultMap = new HashMap<>(3);
+        Map<String, Object> map = new LinkedHashMap<>(5);
+        map.put("screenshot", screenshot);
+        System.out.println(streamURL);
+        // 获取视频源
+        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(streamURL);
+        try {
+            grabber.start();
+            //获取第一帧
+            Frame frame = grabber.grabFrame();
+            if (frame != null) {
+                int flag = 0;
+                while (frame != null && VideoUtil.running.get()) {
+                    //视频快照
+                    frame = grabber.grabImage();
+                    //文件储存对象
+                    String fileName = imagePath + uuid + "_" + flag;
+                    File tempFile = File.createTempFile(fileName, ".jpg");
+                    ImageIO.write(FrameToBufferedImage(frame), "jpg", tempFile);
+                }
+                //停止
+                grabber.stop();
+                //保存图片
+                resultMap.put(uuid, map);
+                //转成JSON存储到redis
+                redisService.redisTemplate.opsForValue().set(uuid,resultMap,60*60);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                grabber.stop();
+            } catch (FrameGrabber.Exception e) {
+                e.printStackTrace();
+            }
+        }
+        VideoUtil.running.set(true);
+    }
+
+
+    /**
+     * 测试:
+     * 1、在D盘中新建一个test文件夹,test中再分成video和img,在video下存入一个视频,并命名为test
+     * D:/test/video     D:/test/img
+     */
+    public static void main(String[] args) {
+        //getVideoStream();
+        String result = String.valueOf(getStreamScreenshot("D://files/video/F28688868_20210317093106.flv", "D://files/img/", "rtmp://58.200.131.2:1935/livetv/hunantv"));
+        System.out.println(result);
+    }
+
+
+    /**
+     * 获取视频流
+     */
+    public static Long getVideoStream(String filePath, String imagePath, String streamURL) {
+        return getStream(filePath, imagePath, streamURL, false);
+    }
+
+    /**
+     * 获取视频流快照
+     */
+    public static Long getStreamScreenshot(String filePath, String imagePath, String streamURL) {
+        return getStream(filePath, imagePath, streamURL, true);
+    }
+
+    /**
+     * 开始执行下拉流截视频流
+     */
+    public static Long getStream(String filePath, String imagePath, String streamURL, boolean screenshot) {
+        VideoUtil videoUtil = new VideoUtil();
+        videoUtil.setFilePath(filePath);
+        videoUtil.setImagePath(imagePath);
+        videoUtil.setStreamURL(streamURL);
+        Long uuid = IdGen.snowflakeId();
+        try {
+            Thread t0 = new Thread(() -> {
+                System.out.println("start...");
+                videoUtil.run(uuid, screenshot);
+            });
+            t0.start();
+            Thread.sleep(55000);
+            System.out.println("stop...");
+            running.set(false);
+        } catch (Throwable t) {
+            System.out.println("Caught in main: " + t);
+            t.printStackTrace();
+        }
+        return uuid;
+    }
+}