Browse Source

摄像头模块完善,添加docker部署

wangxiaofei 9 months ago
parent
commit
bdf0837e32
28 changed files with 1243 additions and 321 deletions
  1. 23 0
      docker/Dockerfile
  2. 1 0
      docker/bk_images.sh
  3. 20 0
      docker/config/mysql/mysqld.cnf
  4. BIN
      docker/docker-24.0.5.tgz.tar
  5. BIN
      docker/docker-24.0.6.tgz
  6. BIN
      docker/docker-compose-linux-x86_64
  7. 175 0
      docker/docker-compose.yml
  8. 111 0
      docker/docker部署流程.md
  9. BIN
      docker/ffmpeg-git-amd64-static.tar.xz
  10. 20 0
      taphole-admin/pom.xml
  11. 0 24
      taphole-admin/src/main/resources/application-dev-example.yml
  12. 7 11
      taphole-admin/src/main/resources/application-dev.yml
  13. 26 9
      taphole-admin/src/main/resources/application-prod.yml
  14. 27 6
      taphole-admin/src/main/resources/application-test.yml
  15. 2 1
      taphole-admin/src/main/resources/loadFFmpeg.properties
  16. 80 76
      taphole-camera/src/main/java/com.sckj.camera/aop/HikCameraAspect.java
  17. 62 2
      taphole-camera/src/main/java/com.sckj.camera/controller/CameraController.java
  18. 13 8
      taphole-camera/src/main/java/com.sckj.camera/hik/HCNetSDK.java
  19. 72 30
      taphole-camera/src/main/java/com.sckj.camera/hik/HCNetTools.java
  20. 390 0
      taphole-camera/src/main/java/com.sckj.camera/hik/HCNetTools2.java
  21. 5 8
      taphole-camera/src/main/java/com.sckj.camera/manager/HikCameraManager.java
  22. 2 0
      taphole-camera/src/main/java/com.sckj.camera/model/dto/CameraDTO.java
  23. 3 3
      taphole-camera/src/main/java/com.sckj.camera/model/dto/ResultDTO.java
  24. 1 1
      taphole-camera/src/main/java/com.sckj.camera/service/CameraFlowServiceImpl.java
  25. 162 141
      taphole-camera/src/main/java/com.sckj.camera/service/CameraServiceImpl.java
  26. 1 1
      taphole-camera/src/main/java/com.sckj.camera/service/FFmpegServiceImpl.java
  27. 20 0
      taphole-camera/src/main/java/com.sckj.camera/util/FlowUtils.java
  28. 20 0
      taphole-common/src/main/java/com/sckj/common/socketio/SocketioProperties.java

+ 23 - 0
docker/Dockerfile

@@ -0,0 +1,23 @@
+# 使用官方的 OpenJDK 基础镜像
+FROM openjdk:8-jre
+
+# 设置工作目录
+WORKDIR /app
+
+# 复制项目依赖文件
+COPY target/taphole-admin-1.0.0.jar /app/springboot-app.jar
+
+
+# [复制 FFmpeg 静态二进制文件]  ffmpeg和ffprobe请务必要和loadFFmpeg.propertis中的path保持一致
+COPY ffmpeg/ffmpeg  /usr/local/ffmpeg/bin/ffmpeg
+COPY ffmpeg/ffprobe /usr/local/ffmpeg/bin/ffprobe
+
+# 确保 FFmpeg 可执行
+RUN chmod +x /usr/local/ffmpeg/bin/ffmpeg /usr/local/ffmpeg/bin/ffprobe
+
+
+# 暴露应用端口
+EXPOSE 8080
+
+# 启动 Spring Boot 应用
+ENTRYPOINT ["java", "-jar", "springboot-app.jar"]

+ 1 - 0
docker/bk_images.sh

@@ -0,0 +1 @@
+docker save -o /home/xiaofei/images.tar openjdk:8-jre mysql:5.7.29 redis:7.0.4 zlmediakit:latest taphole-app:latest

+ 20 - 0
docker/config/mysql/mysqld.cnf

@@ -0,0 +1,20 @@
+[client]
+default-character-set=utf8mb4
+[mysql]
+default-character-set=utf8mb4
+[mysqld]
+skip-host-cache
+skip-name-resolve
+default-authentication-plugin=mysql_native_password
+sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
+table_open_cache=512
+character-set-server=utf8mb4
+collation-server=utf8mb4_general_ci
+default-time-zone='+8:00'
+log-bin=mysql-binlog
+binlog-format=ROW
+server_id=1
+max_connections=1000
+max_user_connections=1000
+thread_cache_size=64
+bind-address = 0.0.0.0

BIN
docker/docker-24.0.5.tgz.tar


BIN
docker/docker-24.0.6.tgz


BIN
docker/docker-compose-linux-x86_64


+ 175 - 0
docker/docker-compose.yml

@@ -0,0 +1,175 @@
+#指定使用的 Docker Compose API 版本
+version: '3'
+
+networks:
+  taphole:
+    driver: bridge
+    ipam:
+      config:
+        - subnet: 172.28.1.0/24
+#定义了要运行的服务列表
+services:
+
+  #  maven:
+  #    container_name: taphole-java-maven
+  #    image: maven:3.8.6-openjdk-8
+  #   # restart: always
+  #    tty: true
+  #    working_dir: /taphole_java/server
+  #    volumes:
+  #      - ./server:/taphole_java/server
+  #    networks:
+  #      - taphole
+  #    ports:
+  #      - "58080:28080"
+  #    command: bash
+  #    depends_on:
+  #      - mysql
+  #      - redis
+  #      - zlmediakit
+
+
+
+  mysql:
+    container_name: taphole-mysql
+    image: mysql:5.7.29 #X86架构
+    #image: amd64/mysql:5.7.29 #arm架构
+    # restart: always
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+    #映射宿主机当前目录到容器
+    volumes:
+      - ./mysql57/initdb:/docker-entrypoint-initdb.d
+      - ./mysql57/data:/var/lib/mysql #数据文件目录挂载
+      - ./mysql57/conf:/etc/mysql/conf.d #配置文件目录挂载
+      - ./mysql57/log:/var/log/mysql #日志文件目录挂载
+    networks:
+      - taphole
+    ports:
+      - "53306:3306"
+    privileged: true
+    #容器启动后执行的命令
+    command: [
+      'mysqld',
+      '--innodb-buffer-pool-size=80M',
+      '--character-set-server=utf8mb4',
+      '--collation-server=utf8mb4_unicode_ci',
+      '--default-time-zone=+8:00',
+      '--lower-case-table-names=1'
+    ]
+
+
+  redis:
+    container_name: taphole-redis
+    image: redis:7.0.4
+    privileged: true
+    # restart: always
+    volumes:
+      - ./redis/data:/data
+    environment:
+      TZ: "Asia/Shanghai"
+    networks:
+      - taphole
+    ports:
+      - "56379:6379"
+    command: redis-server --appendonly yes
+
+  #  node:
+  #    container_name: taphole-node
+  #    image: node:14.18.1
+  #   # restart: always
+  #    volumes:
+  #      - ./node/admin:/taphole_node/admin
+  #    networks:
+  #      - taphole
+  #    tty: true
+  #    working_dir: /taphole_node/admin
+  #    ports:
+  #      - "55173:5173"
+
+  zlmediakit:
+    image: zlmediakit:latest
+    container_name: taphole-zlmediakit
+    ports:
+      - "1935:1935"  # RTMP 端口
+      - "8000:8000"  # HTTP 端口
+      - "8554:8554"  # RTSP 端口
+      - "8083:8083"  # WebRTC 端口
+      - "18000:80"    # HTTP 端口
+    volumes:
+      - ./zlmediakit/config:/opt/zlmediakit/config  # 挂载配置文件目录
+      - ./zlmediakit/media:/opt/zlmediakit/media    # 挂载媒体文件目录
+    networks:
+      taphole:
+        ipv4_address: 172.28.1.10
+
+  #  zlmediakit:
+  #    container_name: taphole-zlmediakit
+  #    image: zlmediakit:latest
+  #  #  restart: always
+  #    volumes:
+  #      - ./zlmediakit/logs:/opt/media/bin/log
+  #      - ./zlmediakit/data/www:/opt/media/bin/www
+  #      - ./zlmediakit/conf/config.ini:/opt/media/conf/config.ini
+  #      - ./zlmediakit/conf/default.pem:/opt/media/bin/default.pem
+  #    expose:
+  #      - "80"
+  #      - "443"
+  #      - "554"
+  #      - "1935"
+  #    ports:
+  #      - "51935:1935"
+  #      - "58082:80"
+  #      - "58443:443"
+  #      - "58554:554"
+  #      - "50000:10000"
+  #      - "50000:10000/udp"
+  #      - "5000:8000/udp"
+  #      - "59000:9000/udp"
+  #      - "50000-50500:30000-30500"
+  #      - "50000-50500:30000-30500/udp"
+  #    networks:
+  #      - taphole
+
+
+  #  nginx:
+  #    image: nginx:1.22
+  #    container_name: taphole-nginx
+  #    volumes:
+  #      - ./nginx/conf:/etc/nginx #配置文件目录挂载
+  #      - ./nginx/html:/usr/share/nginx/html #静态资源根目录挂载
+  #      - ./nginx/logs:/var/log/nginx #日志文件目录挂载
+  #    ports:
+  #      - 5080:80
+  #    networks:
+  #      - taphole
+
+  app:
+    image: taphole-app:latest
+    container_name: taphole-app
+    #   command: java -jar /app/my-springboot-app.jar
+    build:
+      context: .
+      dockerfile: Dockerfile
+    volumes:
+      # - ./target/taphole-admin-1.0.0.jar:/app/my-springboot-app.jar
+      - ./target/loadFFmpeg.properties:/app/loadFFmpeg.properties
+      - /home/xiaofei/taphole/hcsdk/linux:/home/xiaofei/taphole/hcsdk/linux
+      - /home/xiaofei/uploads/taphole/:/home/xiaofei/uploads/taphole/  #映射资源目录
+      # - /usr/local/ffmpeg/bin:/usr/local/ffmpeg/bin
+    ports:
+      - "48080:8080"
+      - "33000:33000"
+    environment:
+      - PREFER_HOST_MODE=hostname
+      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/taphole
+      - SPRING_DATASOURCE_USERNAME=root
+      - SPRING_DATASOURCE_PASSWORD=root
+      - SPRING_REDIS_HOST=redis
+      - SPRING_REDIS_PORT=6379
+    depends_on:
+      - mysql
+      - redis
+      - zlmediakit
+    networks:
+      - taphole

+ 111 - 0
docker/docker部署流程.md

@@ -0,0 +1,111 @@
+一、安装docker
+
+    使用uname -r命令查看下系统内核
+    1.下载docker安装包
+        1.1直接下载具体版本 http://blog.fusuccess.top/software/docker-24.0.5.tgz.tar
+        1.2官网 https://download.docker.com/linux/static/stable/x86_64/
+    2.解压离线包
+        tar xvf docker-24.0.5.tgz.tar
+    3.将解压的docker拷贝或移动到/usr/bin/目录下
+        cp docker/* /usr/bin/
+    4.编写docker.service 文件加入Linux服务当中并开启守护进程
+        vim /etc/systemd/system/docker.service
+      以下为编辑内容:
+      [Unit]
+      Description=Docker Application Container Engine
+      Documentation=https://docs.docker.com
+      After=network-online.target firewalld.service
+      Wants=network-online.target
+        
+      [Service]
+      Type=notify
+      # the default is not to use systemd for cgroups because the delegate issues still
+      # exists and systemd currently does not support the cgroup feature set required
+      # for containers run by docker
+      ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock --selinux-enabled=false --default-ulimit nofile=65536:65536
+      ExecReload=/bin/kill -s HUP $MAINPID
+      # Having non-zero Limit*s causes performance problems due to accounting overhead
+      # in the kernel. We recommend using cgroups to do container-local accounting.
+      LimitNOFILE=infinity
+      LimitNPROC=infinity
+      LimitCORE=infinity
+      # Uncomment TasksMax if your systemd version supports it.
+      # Only systemd 226 and above support this version.
+      #TasksMax=infinity
+      TimeoutStartSec=0
+      # set delegate yes so that systemd does not reset the cgroups of docker containers
+      Delegate=yes
+      # kill only the docker process, not all processes in the cgroup
+      KillMode=process
+      # restart the docker process if it exits prematurely
+      Restart=on-failure
+      StartLimitBurst=3
+      StartLimitInterval=60s
+        
+      [Install]
+      WantedBy=multi-user.target
+    
+    5.添加文件可执行权限
+        chmod +x /etc/systemd/system/docker.service
+    6.配置成功后,重新加载 daemon 服务
+        systemctl daemon-reload
+    7.启动 docker 服务
+        systemctl start docker
+
+
+    
+二、安装docker-compose
+
+    1.获取运行文件方法:
+        方法1:下载到本地
+            1.1 在 [docker-compose](https://github.com/docker/compose/releases)下载最新的对应的系统内核的docker compose文件到本地
+            1.2 将下载的文件改名为docker-compoes并移动到/usr/local/bin
+            #添加执行权限
+            1.3 chmod +x /usr/local/bin/docker-compose
+            
+        方法2:远程下载文件
+            #下载Docker-Compose二进制文件
+            2.1 curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
+            #添加执行权限
+            2.2 chmod +x /usr/local/bin/docker-compose
+    2.docker-compose --version 验证安装是否成功
+
+    
+    常用命令 
+     docker compose up -d -- build   #  --build 选项强制重新构建镜像,即使镜像已经存在。
+     docker compose down   #停止并移除容器和网络
+
+
+三、部署程序的Dockerfile处理
+
+    1. 使用uname -r命令查看下系统内核,然后去https://johnvansickle.com/ffmpeg/去下载对应的静态二进制文件
+    
+    2. 将ffmepg静态二进制文件(如ffmpeg-git-amd64-static.tar.xz)上传到服务器,并执行命令:
+       1.1 解压并删除包
+            tar xvf ffmpeg-git-*-static.tar.xz && rm -rf ffmpeg-git-*-static.tar.xz
+       1.2 #将ffmpeg和ffprobe可执行文件移至/usr/bin方便系统直接调用
+            mv ffmpeg-git-*/ffmpeg  ffmpeg-git-*/ffprobe /usr/bin/
+ 
+    Dockerfile中ADD的路径是相对于Dockerfile文件所有的路径的,要把ADD的目录添加到Dockerfile所在的目录里面
+    Dockerfile建议和docker compose放在统一个目录  
+    
+四.镜像导入导出
+
+    需要导出的镜像:基础镜像 openjdk:8-jre mysql:5.7 redis:7 zlmediakit:latest 
+                  应用镜像 taphole-app:latest
+
+    1.导出镜像
+    
+    docker save -o /home/xiaofei/images.tar openjdk:8-jre mysql:5.7.29 redis:7.0.4 zlmediakit:latest taphole-app:latest
+ 
+    2.导入镜像  
+ 
+    docker load -i /home/xiaofei/images.tar
+    
+五.物理机启动应用程序需要的文件
+    
+    1.ffmpeg静态解压包,内部含有文件:ffmepg  ffprobe
+    2.mysql的配置文件mysql.cnf,初始化数据库语句
+    3.target文件夹,存放jar包和loadFFmpeg.propertis
+
+    

BIN
docker/ffmpeg-git-amd64-static.tar.xz


+ 20 - 0
taphole-admin/pom.xml

@@ -73,6 +73,11 @@
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
                 <version>2.6.4</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                    <includeSystemScope>true</includeSystemScope>
+                    <mainClass>com.sckj.admin.TapholeAdminApplication</mainClass>
+                </configuration>
                 <executions>
                     <execution>
                         <goals>
@@ -81,6 +86,21 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <configuration>
+                    <webResources>
+                        <resource>
+                            <directory>src/main/resources/lib</directory>
+                            <targetPath>WEB-INF/lib/</targetPath>
+                            <includes>
+                                <include>**/*.jar</include>
+                            </includes>
+                        </resource>
+                    </webResources>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 

+ 0 - 24
taphole-admin/src/main/resources/application-dev-example.yml

@@ -1,24 +0,0 @@
-# 项目配置
-like:
-  upload-directory: /www/uploads/likeadmin-java/ # 上传目录
-
-# 框架配置
-spring:
-  # 数据源配置
-  datasource:
-    url: jdbc:mysql://localhost:3306/【库名称】?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
-    type: com.zaxxer.hikari.HikariDataSource # 数据源类型
-    driver-class-name: com.mysql.jdbc.Driver # MySql的驱动
-    username: root # 数据库账号
-    password: root # 数据库密码
-  # Redis配置
-  redis:
-    host: localhost   # Redis服务地址
-    port: 6379        # Redis端口
-    password:         # Redis密码
-    database: 0       # 数据库索引
-
-# Mybatis-plus配置 【是否开启SQL日志输出】
-#mybatis-plus:
-#    configuration:
-#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

+ 7 - 11
taphole-admin/src/main/resources/application-dev.yml

@@ -1,6 +1,6 @@
 # 项目配置
 like:
-  upload-directory: D:/opt/camera
+  upload-directory: D:/opt/camera/
 
 # 服务配置
 server:
@@ -12,16 +12,16 @@ server:
 spring:
   # 数据源配置
   datasource:
-    url: jdbc:mysql://112.124.32.131:3306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
+    url: jdbc:mysql://192.168.110.130:13306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
     type: com.alibaba.druid.pool.DruidDataSource # 数据源类型
     driver-class-name: com.mysql.jdbc.Driver # MySql的驱动
     username: root # 数据库账号
-    password: lghr4ELx6GuN2Xp9 # 数据库密码
+    password: root # 数据库密码
   # Redis配置
   redis:
-    host: 101.37.148.192   # Redis服务地址
-    port: 6379        # Redis端口
-    password: sckj@1234        # Redis密码
+    host: 192.168.110.130   # Redis服务地址
+    port: 16379        # Redis端口
+    password:         # Redis密码
     database: 5       # 数据库索引
 #    lettuce:
 #      pool:
@@ -40,12 +40,8 @@ camera:
     rtmphost: 127.0.0.1:1935
     httphost: 127.0.0.1:80
 
-#rtmp:
-#    rtmphost: 127.0.0.1:1935
-#    httphost: 127.0.0.1:80
-
 socketio:
-  host: 127.0.0.1		#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)
+ # host: 127.0.0.1		#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)
   port: 33000			#监听端口
   maxFramePayloadLength: 1048576
   maxHttpContentLength: 1048576

+ 26 - 9
taphole-admin/src/main/resources/application-prod.yml

@@ -1,29 +1,46 @@
 # 项目配置
 like:
-  upload-directory: /www/uploads/likeadmin-java/ # 上传目录
+  upload-directory: /home/xiaofei/uploads/taphole/ # 上传目录
 
 # 服务配置
 server:
-  port: 8082
+  port: 8080
   servlet:
     context-path: /
 # 框架配置
 spring:
   # 数据源配置
   datasource:
-    url: jdbc:mysql://localhost:13306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
-    type: com.zaxxer.hikari.HikariDataSource # 数据源类型
+    url: jdbc:mysql://mysql:3306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
+    type: com.alibaba.druid.pool.DruidDataSource # 数据源类型
     driver-class-name: com.mysql.jdbc.Driver # MySql的驱动
     username: root # 数据库账号
-    password: root@123 # 数据库密码
+    password: root # 数据库密码
   # Redis配置
   redis:
-    host: 101.37.148.192   # Redis服务地址
+    host: redis   # Redis服务地址
     port: 6379        # Redis端口
-    password: sckj@1234        # Redis密码
-    database: 5       # 数据库索引
+#    password: sckj@1234        # Redis密码
+#    database: 5       # 数据库索引
 
 # Mybatis-plus配置 【是否开启SQL日志输出】
 #mybatis-plus:
 #    configuration:
-#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+camera:
+  filepath: /home/xiaofei/uploads/taphole/
+  rtmp:
+    rtmphost: 192.168.110.130:1935
+    httphost: 192.168.110.130:18000
+
+socketio:
+  #host: 0.0.0.0		#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)
+  port: 33000			#监听端口
+  maxFramePayloadLength: 1048576
+  maxHttpContentLength: 1048576
+  bossCount: 1
+  workCount: 100
+  allowCustomRequests: true
+  upgradeTimeout: 1000000		#协议升级超时时间(毫秒),默认10000。HTTP握手升级为ws协议超时时间
+  pingTimeout: 6000000		    #Ping消息超时时间(毫秒),默认60000,这个时间间隔内没有接收到心跳消息就会发送超时事件
+  pingInterval: 25000			#Ping消息间隔(毫秒),默认25000。客户端向服务器发送一条心跳消息间隔

+ 27 - 6
taphole-admin/src/main/resources/application-test.yml

@@ -1,10 +1,10 @@
 # 项目配置
 like:
-  upload-directory: /www/uploads/likeadmin-java/ # 上传目录
+  upload-directory: /home/xiaofei/uploads/taphole/ # 上传目录
 
 # 服务配置
 server:
-  port: 8082
+  port: 28080
   servlet:
     context-path: /
 
@@ -12,11 +12,11 @@ server:
 spring:
   # 数据源配置
   datasource:
-    url: jdbc:mysql://localhost:13306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
-    type: com.zaxxer.hikari.HikariDataSource # 数据源类型
+    url: jdbc:mysql://192.168.110.130:13306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
+    type: com.alibaba.druid.pool.DruidDataSource # 数据源类型
     driver-class-name: com.mysql.jdbc.Driver # MySql的驱动
     username: root # 数据库账号
-    password: root@123 # 数据库密码
+    password: root # 数据库密码
   # Redis配置
   redis:
     host: 101.37.148.192   # Redis服务地址
@@ -27,4 +27,25 @@ spring:
 # Mybatis-plus配置 【是否开启SQL日志输出】
 #mybatis-plus:
 #    configuration:
-#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+camera:
+  filepath: /home/xiaofei/uploads/taphole/
+  rtmp:
+    rtmphost: 192.168.110.130:1935
+    httphost: 192.168.110.130:80
+
+#rtmp:
+#    rtmphost: 127.0.0.1:1935
+#    httphost: 127.0.0.1:80
+
+socketio:
+#  host: 192.168.110.130	#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)
+  port: 33000			#监听端口
+  maxFramePayloadLength: 1048576
+  maxHttpContentLength: 1048576
+  bossCount: 1
+  workCount: 100
+  allowCustomRequests: true
+  upgradeTimeout: 1000000		#协议升级超时时间(毫秒),默认10000。HTTP握手升级为ws协议超时时间
+  pingTimeout: 6000000		    #Ping消息超时时间(毫秒),默认60000,这个时间间隔内没有接收到心跳消息就会发送超时事件
+  pingInterval: 25000			#Ping消息间隔(毫秒),默认25000。客户端向服务器发送一条心跳消息间隔

+ 2 - 1
taphole-admin/src/main/resources/loadFFmpeg.properties

@@ -1,7 +1,8 @@
 #ffmpeg执行路径,一般为ffmpeg的安装目录,该路径只能是目录,不能为具体文件路径,否则会报错
+# path 必须和Dockerfile中的[复制 FFmpeg 静态二进制文件]保持一致
+#path=/usr/local/ffmpeg/bin/ 该路径为docker部署路径
 #path=/usr/bin/
 path=D:\\project\\xiha\\ffmpeg-2024-10-07-git-496b8d7a13-full_build\\bin\\
-#path=E:/ffmpeg/bin/
 #存放任务的默认Map的初始化大小
 size=10
 #是否输出debug消息

+ 80 - 76
taphole-camera/src/main/java/com.sckj.camera/aop/HikCameraAspect.java

@@ -1,11 +1,13 @@
 package com.sckj.camera.aop;
 
 
+import com.sckj.camera.manager.CameraProperties;
 import com.sckj.camera.service.CameraServiceImpl;
 import com.sckj.camera.model.dto.CameraDTO;
 import com.sckj.camera.model.dto.ResultDTO;
 import com.sckj.camera.model.entity.Camera;
 import com.sckj.camera.hik.HCNetTools;
+import com.sckj.camera.util.FlowUtils;
 import com.sckj.camera.util.HikCameraUtils;
 import com.sckj.common.aop.CameraLogin;
 import com.sckj.common.exception.OperateException;
@@ -22,8 +24,12 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.stereotype.Component;
 
+import javax.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import static com.sckj.common.aop.CameraLogin.Operation.VIDEO_DOWNLOAD;
 
@@ -35,73 +41,74 @@ import static com.sckj.common.aop.CameraLogin.Operation.VIDEO_DOWNLOAD;
 @Component
 public class HikCameraAspect {
 
-    @Autowired
+    @Resource
     private CameraServiceImpl cameraService;
 
+    @Resource
+    private CameraProperties cameraProperties;
+
     private final Logger logger = LoggerFactory.getLogger(HikCameraAspect.class);
 
+    ConcurrentHashMap<Integer, CameraDTO> realStreamMap = new ConcurrentHashMap();
+
     @Around(value = "@annotation(com.sckj.common.aop.CameraLogin)"
             + "|| @within(com.sckj.common.aop.CameraLogin)")
-    public Object aroundCamera(ProceedingJoinPoint joinPoint) throws RuntimeException {
+    public Object aroundCamera(ProceedingJoinPoint joinPoint) throws Throwable {
         Object result = null;
-        try {
-            Object[] args = joinPoint.getArgs();
-            if (args.length < 1) {
-                throw new RuntimeException("无传入参数");
-            }
+        Object[] args = joinPoint.getArgs();
+        if (args.length < 1) {
+            throw new RuntimeException("无传入参数");
+        }
 
-            if (args[0] != null & args[0] instanceof CameraDTO) {
-                CameraLogin cameraLog = getCameraLogin(joinPoint);
-                CameraLogin.Operation type = cameraLog.type();
-                if (!cameraLog.loginCamera()) {
-                    return joinPoint.proceed(args);
-                }
-                CameraDTO cameraDTO = (CameraDTO) args[0];
-                Camera camera = cameraService.getById(cameraDTO.getId());
-                if (camera == null) {
-                    throw new OperateException("未查询到设备ID为" + cameraDTO.getId() + "的设备");
-                }
-                BeanUtils.copyProperties(camera, cameraDTO);
-                //设备登录
-                int code = login(cameraDTO, type);
-                args[0] = cameraDTO;
-                switch (code) {
-                    case 0:
-                        int channelNumber = -1;
-                        //必须是监控设备
-                        if (1 == cameraDTO.getType()) {
-                            //有VCR录像机
-                            if (ObjectUtils.isNotEmpty(cameraDTO.getVcr())) {
-                                channelNumber = HikCameraUtils.getIpcChannel(cameraDTO.getChannelList(), cameraDTO.getIp());
-                            } else {
-                              //没有VCR录像机
-                                channelNumber = HikCameraUtils.analyzeChannel(cameraDTO.getChannelList().get(0));
-                            }
-                        }
-
-                        //所在通道或者在录像机的通道
-                        cameraDTO.setChannelNumber(channelNumber);
-                        //成功登录
-                        result = joinPoint.proceed(args);
-                        break;
-                    case 4:
-                        //获取通道失败
-                        logout(cameraDTO);
-                        result = new ResultDTO("", HikCameraUtils.resultNames[code], "");
-                        break;
-                    default:
-                        //注册失败
-                        result = new ResultDTO("", HikCameraUtils.resultNames[code], "");
-                }
-                return result;
-            } else {
-                throw new OperateException("传入参数异常");
-            }
+        if (args[0] == null || !(args[0] instanceof CameraDTO)) {
+            throw new OperateException("传入参数异常");
+        }
 
-        } catch (Throwable e) {
-            logger.error("目标方法执行异常,目标类:" + joinPoint.getTarget() + "方法:" + joinPoint.getSignature().getName(), e);
-            throw new RuntimeException("系统繁忙,请稍后再试!");
+
+        CameraLogin cameraLog = getCameraLogin(joinPoint);
+        CameraLogin.Operation type = cameraLog.type();
+        if (!cameraLog.loginCamera()) {
+            return joinPoint.proceed(args);
+        }
+        CameraDTO cameraDTO = (CameraDTO) args[0];
+        Camera camera = cameraService.getById(cameraDTO.getId());
+        if (camera == null) {
+            throw new OperateException("未查询到设备ID为" + cameraDTO.getId() + "的设备");
         }
+        BeanUtils.copyProperties(camera, cameraDTO);
+        //设备登录
+        int code = login(cameraDTO, type);
+        args[0] = cameraDTO;
+        logger.info("Camera SDK Login Code:{}",code);
+        switch (code) {
+            case -1:
+                //注册失败
+                result = new ResultDTO("", "注册失败", "");
+                break;
+            case -2:
+                //获取通道失败
+                logout(cameraDTO);
+                result = new ResultDTO("", "通道获取失败", "");
+                break;
+            default:
+                int channelNumber = -1;
+                //必须是监控设备
+                if (1 == cameraDTO.getType()) {
+                    //有VCR录像机
+                    if (ObjectUtils.isNotEmpty(cameraDTO.getVcr())) {
+                        channelNumber = HikCameraUtils.getIpcChannel(cameraDTO.getChannelList(), cameraDTO.getIp());
+                    } else {
+                        //没有VCR录像机
+                        channelNumber = HikCameraUtils.analyzeChannel(cameraDTO.getChannelList().get(0));
+                    }
+                }
+                //所在通道或者在录像机的通道
+                cameraDTO.setChannelNumber(channelNumber);
+                //执行注解方法体的代码,此时还不能注销设备,因为一但注销,耗时操作就被迫终止
+                result = joinPoint.proceed(args);
+               break;
+        }
+        return result;
     }
 
 
@@ -114,7 +121,6 @@ public class HikCameraAspect {
         if (Objects.nonNull(cameraLog)) {
             return cameraLog;
         }
-
         return AnnotationUtils.findAnnotation(signature.getDeclaringType(), CameraLogin.class);
     }
 
@@ -123,18 +129,19 @@ public class HikCameraAspect {
      * @param cameraDTO
      * @return
      */
-    public int login(CameraDTO cameraDTO,  CameraLogin.Operation type) {
+    private int login(CameraDTO cameraDTO, CameraLogin.Operation type) {
+        if (CameraLogin.Operation.REAL_STREAM == type || CameraLogin.Operation.BACK_STREAM == type) {
+            //推流需要检查多媒体服务器是否启动
+            if (!FlowUtils.isRTMPServerRunning(cameraProperties.getRtmp().getRtmphost())) {
+                throw new OperateException("RTMP服务器未启动,推流失败");
+            }
+        }
         cameraDTO.setHcTool(new HCNetTools());
         HCNetTools hcTool = cameraDTO.getHcTool();
 
-        int code = 0;
-        if (hcTool.initDevices() == 1) {
-            code = 1;//初始化失败
-            return code;
-        }
         int regSuc = -1;
         switch (type) {
-            //只用到摄像机功能
+            //只用到摄像机,未使用到磁盘录像机
             case REAL_STREAM:
             case CATCH_PICTURE:
                 regSuc = hcTool.deviceLogin(cameraDTO.getIp(), cameraDTO.getPort(), cameraDTO.getAccount(), cameraDTO.getPassword());
@@ -142,26 +149,23 @@ public class HikCameraAspect {
             default:
                 Camera vcr = cameraService.getById(cameraDTO.getVcrId());
                 if (ObjectUtils.isEmpty(vcr)) {
-                    throw new OperateException(cameraDTO.getNo() + "请配置有效的录像机ID");
+                    throw new OperateException("设备ID为" + cameraDTO.getId() + "请配置有效的录像机ID");
                 }
                 cameraDTO.setVcr(vcr);
                 regSuc = hcTool.deviceLogin(cameraDTO.getVcr().getIp(), cameraDTO.getVcr().getPort(), cameraDTO.getVcr().getAccount(), cameraDTO.getVcr().getPassword());
                 break;
         }
 
-        if (regSuc != 0) {
-            code = regSuc;//注册失败
-            return code;
+        if (regSuc == -1) {
+            return regSuc;
         }
 
-        List<String> channelList;
-        channelList = hcTool.getChannelNumber();
-        cameraDTO.setChannelList(channelList);
-        if (channelList.size() == 0) {
-            code = 4;
+        List<String> channelList = hcTool.getChannelNumber();
+        if(ObjectUtils.isEmpty(channelList)){
+            return -2;
         }
-
-        return code;
+        cameraDTO.setChannelList(channelList);
+        return regSuc;
     }
 
     /***

+ 62 - 2
taphole-camera/src/main/java/com.sckj.camera/controller/CameraController.java

@@ -19,6 +19,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.Date;
+import java.util.List;
 
 @Api(tags = "相机设备")
 @RestController
@@ -82,6 +83,18 @@ public class CameraController {
         return AjaxResult.success(result);
     }
 
+    @ApiOperation("所有可用摄像头推流")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "设备id", paramType = "query", dataType = "Long")
+    })
+    @PostMapping("/startTranscodeList")
+    public AjaxResult startTranscodeList() {
+//        CameraDTO cameraDTO = new CameraDTO();
+//        cameraDTO.setId(id);
+        List<ResultDTO> result = cameraService.startTranscodeList();
+        return AjaxResult.success(result);
+    }
+
     @ApiOperation("开始推流")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "设备id", paramType = "query", dataType = "Long")
@@ -124,10 +137,11 @@ public class CameraController {
     @ApiOperation("抓图")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "设备id", paramType = "query", dataType = "Long"),
-            @ApiImplicitParam(name = "ipc", value = "nvr的ipc摄像机ip地址", paramType = "query", dataType = "String")
     })
     @PostMapping("/catchPic")
-    public AjaxResult catchPic(CameraDTO cameraDTO){
+    public AjaxResult catchPic(Long id){
+        CameraDTO cameraDTO = new CameraDTO();
+        cameraDTO.setId(id);
         ResultDTO result = cameraService.catchPic(cameraDTO);
         return AjaxResult.success(result);
     }
@@ -189,5 +203,51 @@ public class CameraController {
         return AjaxResult.success(result);
     }
 
+    /**
+     * 关闭ffmpeg进程
+     * @param taskName 相机id
+     * @return Boolean
+     */
+    @ApiOperation("任务名关闭进程")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "taskName", value = "taskName", paramType = "query", dataType = "String")
+    })
+    @PostMapping("/stopRtmpByTaskName")
+    public AjaxResult stopRtmpByTaskName(String taskName){
+        Boolean result = cameraService.stopRtmpByTaskName(taskName);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 关闭ffmpeg进程
+     * @param ip 摄像机IP
+     * @return Boolean
+     */
+    @ApiOperation("IP关闭进程")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "ip", value = "ip", paramType = "query", dataType = "String")
+    })
+    @PostMapping("/stopRtmpByIP")
+    public AjaxResult stopRtmpByIP(String ip){
+        Boolean result = cameraService.stopRtmpByIP(ip);
+        return AjaxResult.success(result);
+    }
+
+
+    /**
+     * 获取录像文件信息
+     * @return ResultDTO
+     */
+    @ApiOperation("获取摄像机状态")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "设备id", paramType = "query", dataType = "Long"),
+    })
+    @GetMapping("/getState")
+    public AjaxResult getState(Long id) {
+        CameraDTO cameraDTO = new CameraDTO();
+        cameraDTO.setId(id);
+        Integer result = cameraService.getState(cameraDTO);
+        return AjaxResult.success("成功",result);
+    }
 
 }

+ 13 - 8
taphole-camera/src/main/java/com.sckj.camera/hik/HCNetSDK.java

@@ -28,22 +28,27 @@ import java.util.Arrays;
 import java.util.List;
 
 //SDK接口说明,HCNetSDK.dll
-public interface HCNetSDK extends  StdCallLibrary {
+//windows : extends  StdCallLibrary
+//Linux : extends  Library
+public interface HCNetSDK extends  Library {
+
+	static boolean isLinux() {
+		return System.getProperty("os.name").toLowerCase().contains("linux");
+	}
+
+	static boolean isWindows() {
+		return System.getProperty("os.name").toLowerCase().contains("windows");
+	}
 
 	/**
 	 * 获取硬件库包
 	 * @return
 	 */
 	static String getLibPath(){
-		URL url = HCNetTools.isWindows() ?
-				HCNetSDK.class.getResource( "/lib/win/HCNetSDK.dll") :
-				HCNetSDK.class.getResource( "/lib/linux/libhcnetsdk.so");
-		ClassPathResource resource = new ClassPathResource("/lib/win/HCNetSDK.dll");
-
-		return resource != null ? resource.getPath(): null;
+		return  isWindows() ? System.getProperty("user.dir") + "\\taphole-camera\\src\\main\\resources\\lib\\win\\HCNetSDK.dll":"/home/xiaofei/taphole/hcsdk/linux/libhcnetsdk.so";
 	}
 
-	HCNetSDK INSTANCE =  (HCNetSDK)Native.loadLibrary("D:\\project\\xiha\\taphole\\taphole-camera\\src\\main\\resources\\lib\\win\\HCNetSDK.dll",HCNetSDK.class);
+	HCNetSDK INSTANCE =  (HCNetSDK)Native.loadLibrary(getLibPath(),HCNetSDK.class);
 
 	/***宏定义***/
 	//常量

+ 72 - 30
taphole-camera/src/main/java/com.sckj.camera/hik/HCNetTools.java

@@ -12,9 +12,9 @@ import java.util.HashMap;
 import java.util.List;
 
 public class HCNetTools {
-	private final Logger logger = LoggerFactory.getLogger(getClass());
+	private static final Logger logger = LoggerFactory.getLogger(HCNetTools.class.getName());
 
-	static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
+	public static final HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
 
 	//海康播放库(SDK里的PlayCtrl不是此处的PlayCtrl)
 	//static PlayCtrl playControl = PlayCtrl.INSTANCE;
@@ -40,18 +40,21 @@ public class HCNetTools {
 		m_err = new IntByReference(-1);
 	}
 
-	/**
-	 * 初始化资源配置
-	 */
-	public int initDevices() {
+	static {
 		boolean b = hCNetSDK.NET_DVR_Init();
 		if (!b) {
 			//初始化失败
-			//throw new RuntimeException("初始化失败");
-			System.out.println("初始化失败");
-			return 1;
+			throw new RuntimeException("HCNetSDK初始化失败");
+		}
+		FExceptionCallBack_Imp fExceptionCallBack = new FExceptionCallBack_Imp();
+		Pointer pUser = null;
+		if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
+			logger.info("设置异常消息回调失败");
+		} else {
+			logger.info("设置异常消息回调成功");
+			//启动SDK写日志
+			hCNetSDK.NET_DVR_SetLogToFile(3, "./logs", false);
 		}
-		return 0;
 	}
 
 	/**
@@ -61,13 +64,12 @@ public class HCNetTools {
 	 * @param password 设备登录密码
 	 * @param ip       IP地址
 	 * @param port     端口
-	 * @return 结果
+	 * @return 登录成功返回用户ID,失败返回-1
 	 */
 	public int deviceLogin(String ip, String port,String name, String password) {
-
-		if (bRealPlay) {//判断当前是否在预览
-			return 2;//"注册新用户请先停止当前预览";
-		}
+//		if (bRealPlay) {//判断当前是否在预览
+//			return 2;//"注册新用户请先停止当前预览";
+//		}
 		if (lUserID > -1) {//先注销,在登录
 			hCNetSDK.NET_DVR_Logout_V30(lUserID);
 			lUserID = -1;
@@ -77,15 +79,13 @@ public class HCNetTools {
 		short iPort = Integer.valueOf(port).shortValue();
 		m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();//获取设备参数结构
 		lUserID = hCNetSDK.NET_DVR_Login_V30(ip, iPort, name, password, m_strDeviceInfo);//登录设备
-		long userID = lUserID;
-		if (userID == -1) {
-			int iErr = hCNetSDK.NET_DVR_GetLastError();
-			System.out.println(":注册失败,错误号:" + iErr);
-			System.out.println(hCNetSDK.NET_DVR_GetErrorMsg(m_err));
+		if (lUserID == -1) {
+			logger.error("登录失败,错误码为: " + hCNetSDK.NET_DVR_GetLastError());
+			logger.error(hCNetSDK.NET_DVR_GetErrorMsg(m_err));
 			m_sDeviceIP = "";//登录未成功,IP置为空
-			return 3;//"注册失败";
+			//return 3;//"注册失败";
 		}
-		return 0;
+		return lUserID;
 	}
 
 
@@ -94,18 +94,21 @@ public class HCNetTools {
 	 * 注销登陆
 	 */
 	public void deviceLogout() {
-
 		//如果在预览,先停止预览, 释放句柄
 		if (lPreviewHandle > -1)
 		{
 			hCNetSDK.NET_DVR_StopRealPlay(lPreviewHandle);
+			lPreviewHandle = -1;
 		}
 
 		//如果已经注册,注销
+		//退出程序时调用,每一台设备分别注销
 		if (lUserID> -1) {
 			hCNetSDK.NET_DVR_Logout_V30(lUserID);
+			lUserID = -1;
 		}
-		hCNetSDK.NET_DVR_Cleanup();
+		//SDK反初始化,释放资源,只需要退出时调用一次
+		//hCNetSDK.NET_DVR_Cleanup();
 	}
 
 	/**
@@ -186,6 +189,7 @@ public class HCNetTools {
 			String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
 			logger.info("hksdk(抓图)-抓取失败,错误码:" + m_err.getValue() + "," + err);
 		}
+		deviceLogout();
 		return ok;
 	}
 
@@ -229,10 +233,12 @@ public class HCNetTools {
 			}
 			String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
 			logger.warn("下载异常终止!错误原因:" + m_err.getValue() + "," + err);
+			deviceLogout();
 			return false;
 		} else {
 			String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
 			logger.warn("获取录像文件错误!错误原因:" + m_err.getValue() + "," + err);
+			deviceLogout();
 			return false;
 		}
 	}
@@ -296,10 +302,10 @@ public class HCNetTools {
 						//flag=false;
 					} else {
 						//flag=false;
-						System.out.println("搜索文件结束");
+						logger.info("搜索文件结束");
 						boolean flag2 = hCNetSDK.NET_DVR_FindClose_V30(lFindFile);
 						if (flag2 == false) {
-							System.out.println("结束搜索失败");
+							logger.info("结束搜索失败");
 						}
 					}
 				}
@@ -350,12 +356,48 @@ public class HCNetTools {
 		return structTime;
 	}
 
-	public static boolean isLinux() {
-		return System.getProperty("os.name").toLowerCase().contains("linux");
+
+	/***
+	 * 获取设备状态
+	 * @return 设备的状态,0-正常,1-CPU占用率太高,超过85%,2-硬件错误,例如串口死掉
+	 */
+	public int getState(){
+		HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
+		if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(lUserID, devwork)) {
+			// 返回Boolean值,判断是否获取设备能力
+			logger.info("hksdk(抓图)-返回设备状态失败");
+			return -1;
+		}
+		return devwork.dwDeviceStatic;
 	}
 
-	public static boolean isWindows() {
-		return System.getProperty("os.name").toLowerCase().contains("windows");
+	static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
+
+		@Override
+		public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
+			logger.info(String.format("dwType:%s,lUserID:%s,lHandle:%s",dwType,lUserID,lHandle));
+			logger.info("异常事件类型:" + dwType + ",十六进制:0x" + Integer.toHexString(dwType));
+			switch(dwType)
+			{
+				case HCNetSDK.EXCEPTION_PREVIEW:			//网络预览时异常
+					logger.info("网络预览时网络异常!!!");
+					//TODO: 关闭网络预览
+					break;
+				case HCNetSDK.EXCEPTION_SERIAL:			//透明通道传输时异常
+					logger.info("透明通道传输时网络异常!!!");
+					//TODO: 关闭透明通道
+					break;
+				case HCNetSDK.EXCEPTION_RECONNECT:			//预览时重连
+					logger.info("预览时重连!!!");
+					break;
+				case HCNetSDK.EXCEPTION_EXCHANGE:			//用户交互时异常
+					logger.info("用户交互时异常!!!");
+					break;
+				default:
+					break;
+			}
+			return;
+		}
 	}
 
 }

+ 390 - 0
taphole-camera/src/main/java/com.sckj.camera/hik/HCNetTools2.java

@@ -0,0 +1,390 @@
+//package com.sckj.camera.hik;
+//
+//import com.sun.jna.Pointer;
+//import com.sun.jna.ptr.IntByReference;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//import java.time.LocalDateTime;
+//import java.time.format.DateTimeFormatter;
+//import java.util.ArrayList;
+//import java.util.HashMap;
+//import java.util.List;
+//
+//public class HCNetTools2 {
+//	private static final Logger logger = LoggerFactory.getLogger(HCNetTools2.class.getName());
+//
+//	static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
+//
+//	//海康播放库(SDK里的PlayCtrl不是此处的PlayCtrl)
+//	//static PlayCtrl playControl = PlayCtrl.INSTANCE;
+//
+//	static HCNetSDK.NET_DVR_WORKSTATE_V30 m_strWorkState;
+//	static HCNetSDK.NET_DVR_DEVICEINFO_V30 m_strDeviceInfo;//设备信息
+//	static HCNetSDK.NET_DVR_IPPARACFG m_strIpparaCfg;//IP参数
+//	static HCNetSDK.NET_DVR_CLIENTINFO m_strClientInfo;//用户参数
+//
+//	static boolean bRealPlay;//是否在预览.
+//	static String m_sDeviceIP;//已登录设备的IP地址
+//
+//	static int lUserID;//用户句柄
+//	static int loadHandle;//下载句柄
+//	static int lPreviewHandle;//预览句柄
+//	static IntByReference m_lPort;//回调预览时播放库端口指针
+//	static IntByReference m_err;//错误号
+//
+//	public HCNetTools2() {
+//
+//	}
+//
+//	static {
+//		lUserID = -1;
+//		lPreviewHandle = -1;
+//		m_lPort = new IntByReference(-1);
+//		m_err = new IntByReference(-1);
+//
+//		boolean b = hCNetSDK.NET_DVR_Init();
+//		if (!b) {
+//			//初始化失败
+//			//throw new RuntimeException("初始化失败");
+//			logger.info("初始化失败");
+//		}
+//		FExceptionCallBack_Imp fExceptionCallBack = new FExceptionCallBack_Imp();
+//		Pointer pUser = null;
+//		if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
+//			logger.info("设置异常消息回调失败");
+//		} else {
+//			logger.info("设置异常消息回调成功");
+//			//启动SDK写日志
+//			hCNetSDK.NET_DVR_SetLogToFile(3, "./logs", false);
+//		}
+//	}
+//
+//	/**
+//	 * 初始化资源配置
+//	 */
+////	public int initDevices() {
+////
+////		return 0;
+////	}
+//
+//	/**
+//	 * 设备注册
+//	 *
+//	 * @param name     设备用户名
+//	 * @param password 设备登录密码
+//	 * @param ip       IP地址
+//	 * @param port     端口
+//	 * @return 结果
+//	 */
+//	public static int deviceLogin(String ip, String port,String name, String password) {
+//
+//		if (bRealPlay) {//判断当前是否在预览
+//			return 2;//"注册新用户请先停止当前预览";
+//		}
+//		if (lUserID > -1) {//先注销,在登录
+//			hCNetSDK.NET_DVR_Logout_V30(lUserID);
+//			lUserID = -1;
+//		}
+//		//注册(既登录设备)开始
+//		m_sDeviceIP = ip;
+//		short iPort = Integer.valueOf(port).shortValue();
+//		m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();//获取设备参数结构
+//		lUserID = hCNetSDK.NET_DVR_Login_V30(ip, iPort, name, password, m_strDeviceInfo);//登录设备
+//		long userID = lUserID;
+//		if (userID == -1) {
+//			int iErr = hCNetSDK.NET_DVR_GetLastError();
+//			logger.info(":注册失败,错误号:" + iErr);
+//			logger.info(hCNetSDK.NET_DVR_GetErrorMsg(m_err));
+//			m_sDeviceIP = "";//登录未成功,IP置为空
+//			return 3;//"注册失败";
+//		}
+//		return 0;
+//	}
+//
+//
+//
+//	/**
+//	 * 注销登陆
+//	 */
+//	public static void deviceLogout() {
+//
+//		//如果在预览,先停止预览, 释放句柄
+//		if (lPreviewHandle > -1)
+//		{
+//			hCNetSDK.NET_DVR_StopRealPlay(lPreviewHandle);
+//		}
+//
+//		//如果已经注册,注销
+//		if (lUserID> -1) {
+//			hCNetSDK.NET_DVR_Logout_V30(lUserID);
+//		}
+//		hCNetSDK.NET_DVR_Cleanup();
+//	}
+//
+//	/**
+//	 * 获取设备通道
+//	 */
+//	public static List<String> getChannelNumber() {
+//		List<String> channelList = new ArrayList<>();
+//
+//		IntByReference ibrBytesReturned = new IntByReference(0);//获取IP接入配置参数
+//		boolean bRet = false;
+//		m_strIpparaCfg = new HCNetSDK.NET_DVR_IPPARACFG();
+//		m_strIpparaCfg.write();
+//		Pointer lpIpParaConfig = m_strIpparaCfg.getPointer();
+//		bRet = (m_strDeviceInfo.byIPChanNum != 0);
+//
+//		String devices = "";
+//		if (!bRet) {
+//			//设备不支持,则表示没有IP通道
+//			for (int iChannum = 0; iChannum < m_strDeviceInfo.byChanNum; iChannum++) {
+//				devices = "Camera" + (iChannum + m_strDeviceInfo.byStartChan);
+//				channelList.add(devices);
+//			}
+//		} else {
+//			//设备支持IP通道
+//			boolean ok = hCNetSDK.NET_DVR_GetDVRConfig(lUserID, HCNetSDK.NET_DVR_GET_IPPARACFG, 0, lpIpParaConfig, m_strIpparaCfg.size(), ibrBytesReturned);
+//			if (!ok) {
+//				String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
+//				logger.info("获取配置失败:" + m_err.getValue() + "," + err);
+//			}
+//			m_strIpparaCfg.read();
+//
+//			for (int iChannum = 0; iChannum < m_strDeviceInfo.byChanNum; iChannum++) {
+//				if(m_strIpparaCfg.byAnalogChanEnable[iChannum] == 1)
+//				{
+//					devices = "Camera" + (iChannum + m_strDeviceInfo.byStartChan);
+//					channelList.add(devices);
+//				}
+//			}
+//
+//			for (int iChannum = 0; iChannum < HCNetSDK.MAX_IP_CHANNEL; iChannum++) {
+//				if (m_strIpparaCfg.struIPChanInfo[iChannum].byEnable == 1) {
+//					devices = "IP" + (hCNetSDK.NET_DVR_SDKChannelToISAPI(lUserID,iChannum + m_strDeviceInfo.byStartDChan,true));
+//					String channelIP = (new String(m_strIpparaCfg.struIPDevInfo[iChannum].struIP.sIpV4));
+//					channelIP = channelIP.trim();
+//					devices = devices + "," + channelIP;
+//					channelList.add(devices);
+//				}
+//			}
+//		}
+//
+//		return channelList;
+//	}
+//
+//	/**
+//	 * 抓拍图片
+//	 *
+//	 * @param channel 通道号
+//	 */
+//	public boolean getDVRPic(int channel, String path) {
+//		HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
+//		if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(lUserID, devwork)) {
+//			// 返回Boolean值,判断是否获取设备能力
+//			logger.info("hksdk(抓图)-返回设备状态失败");
+//		}
+//		//图片质量
+//		HCNetSDK.NET_DVR_JPEGPARA jpeg = new HCNetSDK.NET_DVR_JPEGPARA();
+//		//设置图片分辨率
+//		jpeg.wPicSize = 4;
+//		//设置图片质量
+//		jpeg.wPicQuality = 0;
+//		IntByReference a = new IntByReference();
+//		//需要加入通道
+//		//byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
+//		boolean ok = hCNetSDK.NET_DVR_CaptureJPEGPicture(lUserID,channel,jpeg,path);
+//		if (ok) {
+//			logger.info("hksdk(抓图)-结果状态值(0表示成功):" + hCNetSDK.NET_DVR_GetLastError());
+//		} else {
+//			String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
+//			logger.info("hksdk(抓图)-抓取失败,错误码:" + m_err.getValue() + "," + err);
+//		}
+//		return ok;
+//	}
+//
+//	/**
+//	 * 下载录像
+//	 * @return boolean
+//	 */
+//	public boolean downloadBack(LocalDateTime startTime, LocalDateTime endTime, String filePath, int channel) throws InterruptedException {
+//		loadHandle = -1;
+//		loadHandle = hCNetSDK.NET_DVR_GetFileByTime(lUserID, channel, getHkTime(startTime), getHkTime(endTime), filePath);
+//		if (loadHandle >= 0) {
+//			//开始下载
+//			boolean downloadFlag = hCNetSDK.NET_DVR_PlayBackControl(loadHandle, HCNetSDK.NET_DVR_PLAYSTART, 0, null);
+//			int tmp = -1;
+//			IntByReference pos = new IntByReference(0);
+//			while (loadHandle >= 0) {
+//				boolean backFlag = hCNetSDK.NET_DVR_PlayBackControl(loadHandle, HCNetSDK.NET_DVR_PLAYGETPOS, 0, pos);
+//				if (!backFlag) {//防止单个线程死循环
+//					return downloadFlag;
+//				}
+//				int produce = pos.getValue();
+//				if ((produce % 10) == 0 && tmp != produce) {//输出进度
+//					tmp = produce;
+//					logger.info("视频下载进度:" + "==" + produce + "%");
+//				}
+//				if (produce == 100) {//下载成功
+//					hCNetSDK.NET_DVR_StopGetFile(loadHandle);
+//					loadHandle = -1;
+//					String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
+//					logger.info("下载结束,last error: " + m_err.getValue() + "," + err);
+//					return true;
+//				}
+//				if (produce > 100) {//下载失败
+//					hCNetSDK.NET_DVR_StopGetFile(loadHandle);
+//					loadHandle = -1;
+//					String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
+//					logger.warn("下载异常终止!错误原因:" + m_err.getValue() + "," + err);
+//					return false;
+//				}
+//				Thread.sleep(500);
+//			}
+//			String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
+//			logger.warn("下载异常终止!错误原因:" + m_err.getValue() + "," + err);
+//			return false;
+//		} else {
+//			String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
+//			logger.warn("获取录像文件错误!错误原因:" + m_err.getValue() + "," + err);
+//			return false;
+//		}
+//	}
+//
+//	/**
+//	 * 获取录像文件信息
+//	 * @Return:
+//	 */
+//	public List<HashMap<String, String>> getVideoFileList(LocalDateTime startTime, LocalDateTime endTime, int channel) {
+//		List<HashMap<String, String>> fileList = new ArrayList<>();
+//
+//		// 搜索条件
+//		HCNetSDK.NET_DVR_FILECOND m_strFilecond = new HCNetSDK.NET_DVR_FILECOND();
+//
+//		m_strFilecond.struStartTime = getHkTime(startTime);
+//		m_strFilecond.struStopTime = getHkTime(endTime);
+//		m_strFilecond.lChannel = channel;//通道号
+//
+//		int lFindFile = hCNetSDK.NET_DVR_FindFile_V30(lUserID, m_strFilecond);
+//		HCNetSDK.NET_DVR_FINDDATA_V30 strFile;
+//		if (lFindFile < 0) {
+//			String err = hCNetSDK.NET_DVR_GetErrorMsg(m_err);
+//			logger.warn("获取录像文件错误!错误原因:" + m_err.getValue() + "," + err);
+//			return null;
+//		}
+//		int lnext;
+//		strFile = new HCNetSDK.NET_DVR_FINDDATA_V30();
+//		HashMap<String, String> map = new HashMap<>();
+//		boolean flag=true;
+//		while (flag) {
+//			lnext = hCNetSDK.NET_DVR_FindNextFile_V30(lFindFile, strFile);
+//			if (lnext == HCNetSDK.NET_DVR_FILE_SUCCESS) {
+//				//搜索成功
+//				//添加文件名信息
+//				String[] s = new String[2];
+//				s = new String(strFile.sFileName).split("\0", 2);
+//				map.put("fileName:", new String(s[0]));
+//
+//				int iTemp;
+//				String MyString;
+//				if (strFile.dwFileSize < 1024 * 1024) {
+//					iTemp = (strFile.dwFileSize) / (1024);
+//					MyString = iTemp + "K";
+//				} else {
+//					iTemp = (strFile.dwFileSize) / (1024 * 1024);
+//					MyString = iTemp + "M   ";
+//					iTemp = ((strFile.dwFileSize) % (1024 * 1024)) / (1204);
+//					MyString = MyString + iTemp + "K";
+//				}
+//				map.put("fileSize", MyString);                      //添加文件大小信息
+//				map.put("struStartTime", strFile.struStartTime.toStringTime());                      //添加开始时间信息
+//				map.put("struStopTime", strFile.struStopTime.toStringTime());                      //添加结束时间信息
+//				fileList.add(map);
+//			} else {
+//				if (lnext == HCNetSDK.NET_DVR_ISFINDING) {//搜索中
+//					//System.out.println("搜索中");
+//					continue;
+//				} else {
+//					flag=false;
+//					if (lnext == HCNetSDK.NET_DVR_FILE_NOFIND) {
+//						//flag=false;
+//					} else {
+//						//flag=false;
+//						logger.info("搜索文件结束");
+//						boolean flag2 = hCNetSDK.NET_DVR_FindClose_V30(lFindFile);
+//						if (flag2 == false) {
+//							logger.info("结束搜索失败");
+//						}
+//					}
+//				}
+//			}
+//		}
+//		return fileList;
+//	}
+//
+//
+//	/**
+//	 * 获取海康录像机格式的时间
+//	 *
+//	 * @param time
+//	 * @return
+//	 */
+//	public HCNetSDK.NET_DVR_TIME getHkTime(LocalDateTime time) {
+//		HCNetSDK.NET_DVR_TIME structTime = new HCNetSDK.NET_DVR_TIME();
+//
+//		String str = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss").format(time);
+//		String[] times = str.split("-");
+//		structTime.dwYear = Integer.parseInt(times[0]);
+//		structTime.dwMonth = Integer.parseInt(times[1]);
+//		structTime.dwDay = Integer.parseInt(times[2]);
+//		structTime.dwHour = Integer.parseInt(times[3]);
+//		structTime.dwMinute = Integer.parseInt(times[4]);
+//		structTime.dwSecond = Integer.parseInt(times[5]);
+//		return structTime;
+//	}
+//
+//
+//	/***
+//     * 获取设备状态
+//	 * @return 设备的状态,0-正常,1-CPU占用率太高,超过85%,2-硬件错误,例如串口死掉
+//	 */
+//	public int getState(){
+//		HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
+//		if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(lUserID, devwork)) {
+//			// 返回Boolean值,判断是否获取设备能力
+//			logger.info("hksdk(抓图)-返回设备状态失败");
+//			return -1;
+//		}
+//		return devwork.dwDeviceStatic;
+//	}
+//
+//	static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
+//
+//		@Override
+//		public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
+//			logger.info(String.format("dwType:%s,lUserID:%s,lHandle:%s",dwType,lUserID,lHandle));
+//			logger.info("异常事件类型:" + dwType + ",十六进制:0x" + Integer.toHexString(dwType));
+//			switch(dwType)
+//			{
+//				case HCNetSDK.EXCEPTION_PREVIEW:			//网络预览时异常
+//					logger.info("网络预览时网络异常!!!");
+//					//TODO: 关闭网络预览
+//					break;
+//				case HCNetSDK.EXCEPTION_SERIAL:			//透明通道传输时异常
+//					logger.info("透明通道传输时网络异常!!!");
+//					//TODO: 关闭透明通道
+//					break;
+//				case HCNetSDK.EXCEPTION_RECONNECT:			//预览时重连
+//					logger.info("预览时重连!!!");
+//					break;
+//				case HCNetSDK.EXCEPTION_EXCHANGE:			//用户交互时异常
+//					logger.info("用户交互时异常!!!");
+//					break;
+//				default:
+//					break;
+//			}
+//			return;
+//		}
+//	}
+//
+//}

+ 5 - 8
taphole-camera/src/main/java/com.sckj.camera/manager/HikCameraManager.java

@@ -43,26 +43,23 @@ public class HikCameraManager {
             long size = new File(filePath).length();
             cameraFile.setUpdateTime(new Date());
             if (success && size > 0) {
-//                if (size > 0) {
-//                    cameraFileService.saveFileInfo(name,path,size,"2");
-//                } else {
-//                    Files.delete(Paths.get(filePath));
-//                    logger.info("删除文件:" + filePath);
-//                }
                 logger.info("下载成功");
                 cameraFile.setStatus("1");
             } else {
+                Files.delete(Paths.get(filePath));
                 cameraFile.setStatus("2");
                 logger.info("下载失败");
+                logger.info("删除文件:" + filePath);
             }
             cameraFileService.updateById(cameraFile);
         } catch (InterruptedException ex) {
             logger.error("线程异常:", ex);
         } catch (Exception ex2) {
             logger.error("文件删除异常", ex2);
-        } finally {
-            hcTool.deviceLogout();
         }
+//        finally {
+//            hcTool.deviceLogout();
+//        }
     }
 
 }

+ 2 - 0
taphole-camera/src/main/java/com.sckj.camera/model/dto/CameraDTO.java

@@ -22,4 +22,6 @@ public class CameraDTO extends Camera {
     //所属的VCR
     private Camera vcr;
 
+    private String taskName;
+
 }

+ 3 - 3
taphole-camera/src/main/java/com.sckj.camera/model/dto/ResultDTO.java

@@ -8,16 +8,16 @@ import lombok.Data;
 public class ResultDTO {
 
     private String taskName;
-    private Object msg;
+    private Object result;
     private String rtmpUrl;
     private String flvUrl;
     private String hlsUrl;
 
     public ResultDTO() {}
 
-    public ResultDTO(String taskName,String msg,String rtmpUrl) {
+    public ResultDTO(String taskName,String result,String rtmpUrl) {
         this.taskName = taskName;
-        this.msg = msg;
+        this.result = result;
         this.rtmpUrl = rtmpUrl;
     }
      

+ 1 - 1
taphole-camera/src/main/java/com.sckj.camera/service/CameraFlowServiceImpl.java

@@ -42,7 +42,7 @@ public class CameraFlowServiceImpl extends ServiceImpl<CameraFlowMapper, CameraF
     }
 
     public void stopByTaskName(String taskName) {
-
+      //  ffmpegService.stopTranscoding(tasker);
 //        if (StringUtils.isNotBlank(tasker)) {
 //            HTCriteria<CameraFlow> criteria = HTCriteria.getInstance(CameraFlow.class);
 //            criteria.andEqualTo(CameraFlow::getTasker,tasker);

+ 162 - 141
taphole-camera/src/main/java/com.sckj.camera/service/CameraServiceImpl.java

@@ -1,5 +1,6 @@
 package com.sckj.camera.service;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.sckj.camera.hik.HCNetTools;
 import com.sckj.camera.manager.CameraProperties;
@@ -16,21 +17,23 @@ import com.sckj.common.aop.CameraLogin;
 import com.sckj.common.exception.OperateException;
 import com.sckj.common.util.UrlUtils;
 import org.apache.commons.lang3.ObjectUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import javax.annotation.Resource;
 import java.io.File;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 @Service
 @Transactional
@@ -73,91 +76,121 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
                 str.append(cameraDTO.getChannelList().get(i)).append("&");
             }
         }
-        resultDTO.setMsg(str.toString());
+        resultDTO.setResult(str.toString());
         System.out.println(cameraProperties);
         return resultDTO;
     }
 
     /**
-     * 开始推流(rtsp->rtmp)
+     * 所有可用摄像头开始推流(rtsp->rtmp)
      *
-     * @param cameraDTO 相机信息
+     * @param
      * @return ResultDTO
      */
-    @CameraLogin
-    public ResultDTO startTranscode(CameraDTO cameraDTO) {
-        ResultDTO resultDTO = new ResultDTO();
-        if (ObjectUtils.isEmpty(cameraDTO)) {
-            resultDTO.setMsg("未找到设备");
-            return resultDTO;
+   // @PostConstruct
+    public List<ResultDTO> startTranscodeList() {
+        logger.info("自动开启视频推送");
+        QueryWrapper<Camera> queryWrapper = new QueryWrapper<>();
+        //排除禁用的设备
+        queryWrapper.lambda().ne(Camera::getStatus, "0").eq(Camera::getType,"1");
+
+        List<Camera> cameraList = list(queryWrapper);
+        if (ObjectUtils.isEmpty(cameraList)) {
+            return null;
         }
+        List<ResultDTO> resultDTOList = new ArrayList<>();
+        for (Camera camera : cameraList) {
+            ResultDTO resultDTO = new ResultDTO();
+            CameraDTO cameraDTO = new CameraDTO();
+            BeanUtils.copyProperties(camera, cameraDTO);
+            HCNetTools hcNetTools = new HCNetTools();
+            int userId = hcNetTools.deviceLogin(camera.getIp(), camera.getPort(), camera.getAccount(), camera.getPassword());
+            if (userId > -1) {
+                //登录成功
+                List<String> channelNumberList = hcNetTools.getChannelNumber();
+                if (ObjectUtils.isEmpty(channelNumberList)) {
+                    resultDTO.setResult("通道获取失败");
+                } else {
+                    int channelNumber = HikCameraUtils.analyzeChannel(channelNumberList.get(0));
+                    String appName = cameraDTO.getAccount() + cameraDTO.getIp().replace(".", "");
+                    String channelNumberStr = channelNumber + "02"; //新通道(2012年之后设备,02代表子码流)
+                    String rtspName = "rtsp://" + cameraDTO.getAccount() + ":" + cameraDTO.getPassword() + "@" + cameraDTO.getIp() + ":554/" + channelNumberStr + "?transportmode=unicast"; //新码流
+                    pushStream(cameraDTO, appName, rtspName);
+                }
+            } else {
+                resultDTO.setResult("login failed");
+            }
+            resultDTOList.add(resultDTO);
+        }
+        return resultDTOList;
+    }
 
-        String appName = cameraDTO.getAccount() + cameraDTO.getIp().replace(".", "");
-
-//            if (channelNumber == -1) {
-//                logger.info("实时流-未搜索到ip:" + cameraDTO.getIp() + ", ipc:" + cameraDTO.getIpcAddress() + " 的设备");
-//            }
-
+    /***
+     * 推送即时视频并保存到本地
+     * @param cameraDTO
+     * @param appName
+     * @param rtspName
+     * @return
+     */
+    private ResultDTO pushStream(CameraDTO cameraDTO, String appName, String rtspName) {
         String rtmpUrl = "rtmp://" + cameraProperties.getRtmp().getRtmphost() + "/live/" + appName; //rtmp链
-        String flvUrl = "http://" + cameraProperties.getRtmp().getHttphost() + "/live/" + appName + ".flv"; //http-flv链
+        String flvUrl = "http://" + cameraProperties.getRtmp().getHttphost() + "/live/" + appName + ".live.flv"; //live-flv链
         String hlsUrl = "http://" + cameraProperties.getRtmp().getHttphost() + "/live/" + appName + "/hls.m3u8"; //hls链
 
-        boolean runFlag = ffmpegService.taskerIsRun(appName);
         CameraFlow cameraFlow = flowService.findByRtmpUrl(rtmpUrl);
-        if (cameraFlow != null && !runFlag) {
+        if (cameraFlow != null) {
+            //终止推流
+            ffmpegService.stopTranscoding(appName);
+            //删除流路径
             flowService.removeById(cameraFlow.getId());
         }
 
-        if (runFlag) {
-            //已推流
-            if (cameraFlow != null) {
-                resultDTO.setTaskName(appName);
-                resultDTO.setMsg(HikCameraUtils.resultNames[0]);
-                resultDTO.setRtmpUrl(rtmpUrl);
-                resultDTO.setFlvUrl(flvUrl);
-                resultDTO.setHlsUrl(hlsUrl);
-                cameraFlow.setClient(cameraFlow.getClient() + 1);
-                flowService.updateById(cameraFlow);
-            } else {
-                //停止推流
-                flowService.stopByTaskName(appName);
-                resultDTO.setMsg("推流失败");
-                return resultDTO;
-            }
+        //开始推流
+        String taskName = ffmpegService.startTranscoding(appName, rtspName, rtmpUrl);
+        ResultDTO resultDTO = new ResultDTO();
+        if (ObjectUtils.isNotEmpty(taskName)) {
+            resultDTO.setTaskName(taskName);
+            resultDTO.setResult("success");
+            resultDTO.setRtmpUrl(rtmpUrl);
+            resultDTO.setFlvUrl(flvUrl);
+            resultDTO.setHlsUrl(hlsUrl);
+
+            cameraFlow = new CameraFlow();
+            cameraFlow.setCameraId(cameraDTO.getId());
+            cameraFlow.setTaskName(taskName);
+            cameraFlow.setFlowType(FlowUtils.FlowType.HIK.ordinal());
+            cameraFlow.setRtsp(rtspName);
+            cameraFlow.setRtmp(rtmpUrl);
+            cameraFlow.setFlv(flvUrl);
+            cameraFlow.setHls(hlsUrl);
+            cameraFlow.setClient(1);
+            cameraFlow.setCreateTime(new Date());
+            cameraFlow.setUpdateTime(new Date());
+            flowService.save(cameraFlow);
         } else {
-            //未推流
-            String channelNumberStr = cameraDTO.getChannelNumber() + "02"; //新通道(2012年之后设备,02代表子码流)
-
-            String rtspName = cameraDTO.getAccount() + ":" + cameraDTO.getPassword() + "@" + cameraDTO.getIp() + ":554/" + channelNumberStr + "?transportmode=unicast"; //新码流
-            String rtmpName = cameraProperties.getRtmp().getRtmphost() + "/live/"; //rtmp服务器推流入口
-
-            String tasker = ffmpegService.startTranscoding(appName, rtspName, rtmpName);
-            if (StringUtils.isNotEmpty(tasker)) {
-                resultDTO.setTaskName(tasker);
-                resultDTO.setMsg(HikCameraUtils.resultNames[0]);
-                resultDTO.setRtmpUrl(rtmpUrl);
-                resultDTO.setFlvUrl(flvUrl);
-                resultDTO.setHlsUrl(hlsUrl);
-
-                //添加流记录
-                cameraFlow = new CameraFlow();
-                cameraFlow.setCameraId(cameraDTO.getId());
-                cameraFlow.setTaskName(tasker);
-                cameraFlow.setFlowType(FlowUtils.FlowType.HIK.ordinal());
-                cameraFlow.setRtsp("rtsp://" + rtspName);
-                cameraFlow.setRtmp(rtmpUrl);
-                cameraFlow.setFlv(flvUrl);
-                cameraFlow.setClient(1);
-                cameraFlow.setCreateTime(new Date());
-                cameraFlow.setUpdateTime(new Date());
-                flowService.save(cameraFlow);
-            } else {
-                resultDTO.setMsg("推流失败");
-                return resultDTO;
-            }
+            resultDTO.setResult("failed");
         }
         return resultDTO;
+    }
 
+    /**
+     * 开始推流(rtsp->rtmp)
+     *
+     * @param cameraDTO 相机信息
+     * @return ResultDTO
+     */
+    @CameraLogin
+    public ResultDTO startTranscode(CameraDTO cameraDTO) {
+        ResultDTO resultDTO = new ResultDTO();
+        if (ObjectUtils.isEmpty(cameraDTO)) {
+            resultDTO.setResult("未找到该设备");
+            return resultDTO;
+        }
+        String appName = cameraDTO.getAccount() + cameraDTO.getIp().replace(".", "");
+        String channelNumberStr = cameraDTO.getChannelNumber() + "02"; //新通道(2012年之后设备,02代表子码流)
+        String rtspName = "rtsp://" + cameraDTO.getAccount() + ":" + cameraDTO.getPassword() + "@" + cameraDTO.getIp() + ":554/" + channelNumberStr + "?transportmode=unicast"; //新码流
+        resultDTO = pushStream(cameraDTO, appName, rtspName);
+        return resultDTO;
     }
 
     /**
@@ -185,83 +218,23 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
             //摄像机
             appName = "history" + cameraDTO.getVcr().getAccount() + cameraDTO.getVcr().getIp().replace(".", "") + "to" + cameraDTO.getIp().replace(".", "") + "start" + startTime + "end" + endTime;
         } else {
-            throw new OperateException("目前只支持录像机回放数据");
+            throw new OperateException("目前只支持带有录像机回放数据");
             //NVR
             //appName = "history" + cameraDTO.getAccount() + cameraDTO.getIp().replace(".", "") + "to" + cameraDTO.getIpcAddress().replace(".", "") + "start" + startTime + "end" + endTime;
             //channelNumber = HikCameraUtils.getIpcChannel(cameraDTO.getChannelList(), cameraDTO.getIpcAddress());
         }
-
 //        if (channelNumber == -1) {
 //            logger.info("历史流-未搜索到ip:" + cameraDTO.getIp() + ", ipc:" + cameraDTO.getIpcAddress() + " 的设备");
 //        }
-        String rtmpUrl = "rtmp://" + cameraProperties.getRtmp().getRtmphost() + "/live/" + appName; //rtmp链
-        String flvUrl = "http://" + cameraProperties.getRtmp().getHttphost() + "/live/" + appName + ".flv"; //http-flv链
-        String hlsUrl = "http://" + cameraProperties.getRtmp().getHttphost() + "/live/" + appName + "/hls.m3u8"; //hls链
-
-        boolean runFlag = ffmpegService.taskerIsRun(appName);
-        CameraFlow cameraFlow = flowService.findByRtmpUrl(rtmpUrl);
-        if (cameraFlow != null && !runFlag) {
-            flowService.removeById(cameraFlow.getId());
-        }
-
-        if (runFlag) {
-            //已推流
-            if (cameraFlow != null) {
-                resultDTO.setTaskName(appName);
-                resultDTO.setMsg(HikCameraUtils.resultNames[0]);
-                resultDTO.setRtmpUrl(rtmpUrl);
-                resultDTO.setFlvUrl(flvUrl);
-                resultDTO.setHlsUrl(hlsUrl);
-                cameraFlow.setClient(cameraFlow.getClient() + 1);
-                flowService.updateById(cameraFlow);
-            } else {
-                //停止推流
-                flowService.stopByTaskName(appName);
-                resultDTO.setMsg("推流失败");
-                return resultDTO;
-            }
+        //未推流
+        String channelNumberStr = cameraDTO.getChannelNumber() + "01"; //回放流只有主码流
+        String rtspName = "rtsp://";
+        if (endTime != null) {
+            rtspName += cameraDTO.getVcr().getAccount() + ":" + cameraDTO.getVcr().getPassword() + "@" + cameraDTO.getVcr().getIp() + ":554/Streaming/tracks/" + channelNumberStr + "?starttime=" + startTime + "&endtime=" + endTime;
         } else {
-            //未推流
-            String channelNumberStr = cameraDTO.getChannelNumber() + "01"; //回放流只有主码流
-            String rtspName;
-            if (endTime != null) {
-                rtspName = cameraDTO.getVcr().getAccount() + ":" + cameraDTO.getVcr().getPassword() + "@" + cameraDTO.getVcr().getIp() + ":554/Streaming/tracks/" + channelNumberStr + "?starttime=" + startTime + "&endtime=" + endTime;
-            } else {
-                rtspName = cameraDTO.getVcr().getAccount() + ":" + cameraDTO.getVcr().getPassword() + "@" + cameraDTO.getVcr().getIp() + ":554/Streaming/tracks/" + channelNumberStr + "?starttime=" + startTime;
-            }
-            String rtmpName = cameraProperties.getRtmp().getRtmphost() + "/live/"; //rtmp服务器推流入口
-
-            String tasker = ffmpegService.startTranscoding(appName, rtspName, rtmpName);
-            if (StringUtils.isNotEmpty(tasker)) {
-                resultDTO.setTaskName(tasker);
-                resultDTO.setMsg(HikCameraUtils.resultNames[0]);
-                resultDTO.setRtmpUrl(rtmpUrl);
-                resultDTO.setFlvUrl(flvUrl);
-                resultDTO.setHlsUrl(hlsUrl);
-
-                //添加流记录
-                cameraFlow = new CameraFlow();
-                cameraFlow.setCameraId(cameraDTO.getId());
-//                    if (cameraDTO.getType() == 0) {
-//                        cameraFlow.setCameraIpc(cameraDTO.getIp());
-//                    } else {
-//                        cameraFlow.setCameraIpc(cameraDTO.getIpcAddress());
-//                    }
-                cameraFlow.setTaskName(tasker);
-                cameraFlow.setFlowType(FlowUtils.FlowType.HIK.ordinal());
-                cameraFlow.setRtsp("rtsp://" + rtspName);
-                cameraFlow.setRtmp(rtmpUrl);
-                cameraFlow.setFlv(flvUrl);
-                cameraFlow.setHls(hlsUrl);
-                cameraFlow.setClient(1);
-                cameraFlow.setCreateTime(new Date());
-                cameraFlow.setUpdateTime(new Date());
-                flowService.save(cameraFlow);
-            } else {
-                resultDTO.setMsg("推流失败");
-                return resultDTO;
-            }
+            rtspName += cameraDTO.getVcr().getAccount() + ":" + cameraDTO.getVcr().getPassword() + "@" + cameraDTO.getVcr().getIp() + ":554/Streaming/tracks/" + channelNumberStr + "?starttime=" + startTime;
         }
+        resultDTO = pushStream(cameraDTO, appName, rtspName);
         return resultDTO;
     }
 
@@ -274,7 +247,7 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
     public ResultDTO catchPic(CameraDTO cameraDTO) {
         ResultDTO resultDTO = new ResultDTO();
         if (ObjectUtils.isEmpty(cameraDTO)) {
-            resultDTO.setMsg("未找到设备");
+            resultDTO.setResult("未找到设备");
             return resultDTO;
         }
 
@@ -284,7 +257,7 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
             pathName = cameraDTO.getIp().replace(".", "");
         } else {
             //NVR
-            resultDTO.setMsg("NVR设备不支持抓图");
+            resultDTO.setResult("NVR设备不支持抓图");
             return resultDTO;
         }
 
@@ -299,7 +272,7 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
         cameraFile.setUpdateTime(new Date());
         long size = new File(filePath).length();
         if (success && size > 0) {
-            resultDTO.setMsg(UrlUtils.toAbsoluteUrl(path + name));
+            resultDTO.setResult(UrlUtils.toAbsoluteUrl(path + name));
             cameraFile.setStatus("1");
         } else {
             cameraFile.setStatus("2");
@@ -331,11 +304,11 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
         cameraFileService.pathCreator(cameraProperties.getFilepath() + path);
         Integer fileId = cameraFileService.findByPathAndName(path, name);
         if (fileId > 0) {
-            resultDTO.setMsg(fileId.toString());
+            resultDTO.setResult(fileId.toString());
             return resultDTO;
         }
         hikCameraManager.downloadVideoAsync(cameraDTO, path, name, cameraDTO.getChannelNumber() + 32);
-        resultDTO.setMsg(UrlUtils.toAbsoluteUrl(path + name));
+        resultDTO.setResult(UrlUtils.toAbsoluteUrl(path + name));
         return resultDTO;
     }
 
@@ -362,10 +335,10 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
 //            }
             HCNetTools hcTool = cameraDTO.getHcTool();
             List<HashMap<String, String>> map = hcTool.getVideoFileList(cameraDTO.getHistoryDTO().getStartTime(), cameraDTO.getHistoryDTO().getEndTime(), cameraDTO.getChannelNumber() + 32);
-            resultDTO.setMsg(map);
+            resultDTO.setResult(map);
             return resultDTO;
         } else {
-            resultDTO.setMsg("未找到设备");
+            resultDTO.setResult("未找到设备");
             return resultDTO;
         }
     }
@@ -387,4 +360,52 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
         }
     }
 
+    /**
+     * 停止推流
+     *
+     * @param taskName 执行任务名
+     */
+    public Boolean stopRtmpByTaskName(String taskName) {
+        if (ObjectUtils.isEmpty(taskName)) {
+            return true;
+        }
+        return ffmpegService.stopTranscoding(taskName);
+    }
+    /**
+     * 停止推流
+     *
+     * @param ip 执行任务名
+     */
+    public Boolean stopRtmpByIP(String ip) {
+        if (ObjectUtils.isEmpty(ip)) {
+            return false;
+        }
+        QueryWrapper<CameraFlow> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().likeLeft(CameraFlow::getTaskName,ip.replace(".",""));
+        List<CameraFlow> list = flowService.list(queryWrapper);
+        if(ObjectUtils.isEmpty(list)){
+            return false;
+        }
+        return ffmpegService.stopTranscoding(list.get(0).getTaskName());
+    }
+
+    @CameraLogin(type = CameraLogin.Operation.CATCH_PICTURE)
+    public Integer getState(CameraDTO cameraDTO) {
+        if (ObjectUtils.isEmpty(cameraDTO.getHcTool())) {
+            return null;
+        }
+        return cameraDTO.getHcTool().getState();
+    }
+
+
+    @PreDestroy
+    public void closeRealStream() {
+        logger.info("关闭推送流");
+        for (CameraFlow cameraFlow : flowService.list()) {
+            String taskName = cameraFlow.getTaskName();
+            ffmpegService.stopTranscoding(taskName);
+        }
+    }
+
+
 }

+ 1 - 1
taphole-camera/src/main/java/com.sckj.camera/service/FFmpegServiceImpl.java

@@ -30,7 +30,7 @@ public class FFmpegServiceImpl {
             return appName; //如果进程存在,则直接返回进程名
         }
         // 执行任务,id就是appName,如果执行失败返回为null
-        String start = manager.start(appName,"ffmpeg -i rtsp://" + rtspName + " -c copy -f flv rtmp://" + rtmpName + appName);
+        String start = manager.start(appName,"ffmpeg -i " + rtspName + "  -probesize 5000000 -analyzeduration 5000000   -max_delay 5000000  -threads 10 -c copy -f flv " + rtmpName);
         return start;
     }
 

+ 20 - 0
taphole-camera/src/main/java/com.sckj.camera/util/FlowUtils.java

@@ -1,7 +1,27 @@
 package com.sckj.camera.util;
 
+import java.io.IOException;
+import java.net.Socket;
+
 public class FlowUtils {
     public enum FlowType {
         NONE,HIK,GB
     }
+
+    public static boolean isRTMPServerRunning(String rtmpUrl) {
+        try {
+            // 提取 RTMP 服务器的主机和端口
+//            String host = rtmpUrl.split("/")[2].split(":")[0];
+//            int port = Integer.parseInt(rtmpUrl.split("/")[2].split(":")[1]);
+            String host = rtmpUrl.split(":")[0];
+            int port = Integer.parseInt(rtmpUrl.split(":")[1]);
+            // 尝试连接到 RTMP 服务器
+            Socket socket = new Socket(host, port);
+            socket.close();
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
 }

+ 20 - 0
taphole-common/src/main/java/com/sckj/common/socketio/SocketioProperties.java

@@ -27,6 +27,10 @@ public class SocketioProperties {
 
     private int pingInterval;
 
+    private int maxFramePayloadLength;
+
+    private int maxHttpContentLength;
+
     public String getHost() {
         return host;
     }
@@ -90,4 +94,20 @@ public class SocketioProperties {
     public void setPingInterval(int pingInterval) {
         this.pingInterval = pingInterval;
     }
+
+    public int getMaxFramePayloadLength() {
+        return maxFramePayloadLength;
+    }
+
+    public void setMaxFramePayloadLength(int maxFramePayloadLength) {
+        this.maxFramePayloadLength = maxFramePayloadLength;
+    }
+
+    public int getMaxHttpContentLength() {
+        return maxHttpContentLength;
+    }
+
+    public void setMaxHttpContentLength(int maxHttpContentLength) {
+        this.maxHttpContentLength = maxHttpContentLength;
+    }
 }