From 19144edf60036ad3b9a30b1b92e985de69231137 Mon Sep 17 00:00:00 2001
From: zhaowenyu <35589726@qq.com>
Date: Fri, 21 Nov 2025 08:15:05 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev' into dev
---
ard-work/pom.xml | 6
ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java | 175 +++++++++++++++++--------
ard-admin/src/main/resources/application-daqing.yml | 81 +++++++++++
ard-admin/src/main/resources/application-daqing-druid.yml | 61 ++++++++
ard-work/src/main/java/com/ard/work/util/osSelect.java | 39 +++++
ard-admin/src/main/resources/application.yml | 3
6 files changed, 302 insertions(+), 63 deletions(-)
diff --git a/ard-admin/src/main/resources/application-daqing-druid.yml b/ard-admin/src/main/resources/application-daqing-druid.yml
new file mode 100644
index 0000000..b7a87c3
--- /dev/null
+++ b/ard-admin/src/main/resources/application-daqing-druid.yml
@@ -0,0 +1,61 @@
+# 数据源配置
+spring:
+ datasource:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driverClassName: com.mysql.cj.jdbc.Driver
+ druid:
+ # 主库数据源
+ master:
+ url: jdbc:mysql://192.168.10.103:3306/ard-uav?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ username: root
+ password: Ard12345*
+ # 从库数据源
+ slave:
+ # 从数据源开关/默认关闭
+ enabled: false
+ url:
+ username:
+ password:
+ # 初始连接数
+ initialSize: 5
+ # 最小连接池数量
+ minIdle: 10
+ # 最大连接池数量
+ maxActive: 20
+ # 配置获取连接等待超时的时间
+ maxWait: 60000
+ # 配置连接超时时间
+ connectTimeout: 30000
+ # 配置网络超时时间
+ socketTimeout: 60000
+ # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+ timeBetweenEvictionRunsMillis: 60000
+ # 配置一个连接在池中最小生存的时间,单位是毫秒
+ minEvictableIdleTimeMillis: 300000
+ # 配置一个连接在池中最大生存的时间,单位是毫秒
+ maxEvictableIdleTimeMillis: 900000
+ # 配置检测连接是否有效
+ validationQuery: SELECT 1 FROM DUAL
+ testWhileIdle: true
+ testOnBorrow: false
+ testOnReturn: false
+ webStatFilter:
+ enabled: true
+ statViewServlet:
+ enabled: true
+ # 设置白名单,不填则允许所有访问
+ allow:
+ url-pattern: /druid/*
+ # 控制台管理用户名和密码
+ login-username: ruoyi
+ login-password: 123456
+ filter:
+ stat:
+ enabled: true
+ # 慢SQL记录
+ log-slow-sql: true
+ slow-sql-millis: 1000
+ merge-sql: true
+ wall:
+ config:
+ multi-statement-allow: true
\ No newline at end of file
diff --git a/ard-admin/src/main/resources/application-daqing.yml b/ard-admin/src/main/resources/application-daqing.yml
new file mode 100644
index 0000000..c630210
--- /dev/null
+++ b/ard-admin/src/main/resources/application-daqing.yml
@@ -0,0 +1,81 @@
+# 开发环境配置
+server:
+ # 服务器的HTTP端口,默认为8080
+ port: 8080
+
+# token配置
+token:
+ # 是否允许账户多终端同时登录(true允许 false不允许)
+ soloLogin: false
+
+# 日志配置
+logging:
+ level:
+ com.ard: info
+ org.springframework: warn
+
+## knife4j配置
+knife4j:
+ # knife4j增强功能,开启后下面参数才生效
+ enable: true
+ #是否为生产模式 项目部属时为true 防止接口暴露
+ production: false
+ # 配置用户密码
+ basic:
+ enable: false
+ username: admin
+ password: ardkj12345
+
+# 地图资源配置
+geoserver:
+ deviceCoordinateType: WGS84
+ tifPath: H:\map\hlj.tif
+
+wayline:
+ #可理解为100米大小占图像的比例
+ targetRatio: 0.5
+ #最大航段距离
+ maxLength: 2000
+ #最小航段距离
+ minLength: 1
+ #随飞随动分段长度
+ PeerAtLength: 10
+ #航线KMZ临时存放路径
+ waylineSavePath: /home/ard/myworkspace/uav_server/mainserver/waylines
+
+#上云API
+cloud-api:
+ app:
+ id: 143640
+ key: da5babf38a5d05b0f46a7d82910da1b
+ license: khjd7FzUTzJgaArNv97XdA2nKQtYfESOvBHLqjC6MpvPqxCfuQ82nOlTgzoiLp7UoZujARopN2A1ctPFaJ8oHiHQrn3IRV4wqN3hdSM40xXj02YVvTWQBT54WS8Ks+E8Zatp3gk2T0+7cM5a5a3mWzxLMv7CNqw2z9pFBoOBdpY=
+ host: 192.168.10.103
+ port: 123
+
+#流媒体
+mediamtx:
+ apiHost: 192.168.10.103
+ playHost: 192.168.10.103
+ https: true
+
+# mqtt配置
+mqtt:
+ protocol: tcp
+ host: 192.168.10.103
+ port: 1883
+ username: admin
+ password: public
+ client-id: ard-uav
+ inbound-topic: sys/product/+/status,thing/product/+/requests
+
+# oss配置
+oss:
+ enable: true
+ provider: minio
+ endpoint: https://192.168.10.103:9001
+ access-key: admin
+ secret-key: ard12345
+ bucket: cloud-bucket
+ expire: 3600
+ region: us-east-1
+ object-dir-prefix: wayline
diff --git a/ard-admin/src/main/resources/application.yml b/ard-admin/src/main/resources/application.yml
index 7e78448..243a257 100644
--- a/ard-admin/src/main/resources/application.yml
+++ b/ard-admin/src/main/resources/application.yml
@@ -163,5 +163,6 @@
# 激活配置文件
profiles:
+ active: daqing-druid,daqing
#active: prod-druid,prod
- active: dev-druid,dev
\ No newline at end of file
+ #active: dev-druid,dev
\ No newline at end of file
diff --git a/ard-work/pom.xml b/ard-work/pom.xml
index 8424eb8..ef3f0a4 100644
--- a/ard-work/pom.xml
+++ b/ard-work/pom.xml
@@ -113,12 +113,6 @@
<groupId>com.ard</groupId>
<artifactId>ard-oss</artifactId>
</dependency>
- <!-- JavaCV -->
- <dependency>
- <groupId>org.bytedeco</groupId>
- <artifactId>javacv-platform</artifactId>
- <version>1.5.8</version>
- </dependency>
</dependencies>
<repositories>
<repository>
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java b/ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java
index 31fccef..cb0a57b 100644
--- a/ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java
+++ b/ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java
@@ -16,28 +16,26 @@
import com.ard.work.mapper.ArdMediaMapper;
import com.ard.work.service.IArdFlightTaskService;
import com.ard.work.service.IArdServerInfoService;
+import com.ard.work.util.osSelect;
import lombok.extern.slf4j.Slf4j;
-import org.bytedeco.javacv.FFmpegFrameGrabber;
-import org.bytedeco.javacv.Frame;
-import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
-import javax.imageio.ImageIO;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
-import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -175,7 +173,7 @@
}
String boxSn = flightTask.getBoxSn();
for (String videoPath : videoPathList) {
-
+ //videoPath = "https://192.168.1.3:9001/record/DBM250974065008/8212fd8e89814d66b6c32f48d05f5101.mp4";
ArdMedia media = ArdMedia.builder()
.id(IdUtils.simpleUUID())
.taskId(flightTask.getTaskId())
@@ -185,6 +183,7 @@
.build();
// ---- 1. 视频上传(失败不抛异常,返回 null) ----
+
CompletableFuture<String> videoFuture = CompletableFuture.supplyAsync(() -> {
try (InputStream inputStream = openUrlInputStream(videoPath, 5000, 10000)) {
if (inputStream != null) {
@@ -201,9 +200,10 @@
return null;
}, threadPoolTaskExecutor);
// ---- 2. 缩略图上传(失败不抛异常,返回 null) ----
+
CompletableFuture<String> thumbFuture = CompletableFuture.supplyAsync(() -> {
try {
- try (ByteArrayInputStream thumbStream = generateThumbnail(videoPath, 2, 200)) {
+ try (ByteArrayInputStream thumbStream = generateThumbnail(videoPath, 1, 200)) {
if (thumbStream != null) {
return ossServiceContext.upload(
"record",
@@ -278,7 +278,7 @@
// ---- 2. 缩略图上传(失败不抛异常,返回 null) ----
CompletableFuture<String> thumbFuture = CompletableFuture.supplyAsync(() -> {
try {
- try (ByteArrayInputStream thumbStream = generateThumbnail(videoPath, 2, 200)) {
+ try (ByteArrayInputStream thumbStream = generateThumbnail(videoPath, 1, 200)) {
if (thumbStream != null) {
return ossServiceContext.upload(
"record",
@@ -415,66 +415,129 @@
}
// 生成视频缩略图
- public ByteArrayInputStream generateThumbnail(String videoFile, int skipFrames, int targetWidth) {
- long start = System.currentTimeMillis();
- FFmpegFrameGrabber grabber = null;
+ public ByteArrayInputStream generateThumbnail1(String videoFile, int skipFrames, int targetWidth) {
+// long start = System.currentTimeMillis();
+// FFmpegFrameGrabber grabber = null;
+//
+// try {
+// grabber = new FFmpegFrameGrabber(videoFile);
+// grabber.start();
+//
+// int totalFrames = grabber.getLengthInFrames();
+// System.out.println("视频总帧数: " + totalFrames);
+// System.out.println("视频时长: " + grabber.getLengthInTime() / 1000000 + "秒");
+//
+// Frame frame = null;
+// int currentFrame = 0;
+//
+// // 读取指定帧数后的第一帧有效图像
+// while (currentFrame < totalFrames) {
+// frame = grabber.grabImage();
+// if (frame == null) {
+// break;
+// }
+//
+// if (currentFrame > skipFrames && frame.image != null) {
+// break;
+// }
+// currentFrame++;
+// }
+//
+// if (frame == null || frame.image == null) {
+// throw new Exception("无法获取有效视频帧");
+// }
+//
+// // 转换 Frame 为 BufferedImage
+// Java2DFrameConverter converter = new Java2DFrameConverter();
+// BufferedImage image = converter.getBufferedImage(frame);
+//
+// // 按目标宽度等比缩放
+// int targetHeight = (int) ((double) targetWidth / image.getWidth() * image.getHeight());
+// BufferedImage scaledImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_3BYTE_BGR);
+// scaledImage.getGraphics().drawImage(image, 0, 0, targetWidth, targetHeight, null);
+//
+// // 转成 ByteArrayInputStream
+// ByteArrayOutputStream baos = new ByteArrayOutputStream();
+// ImageIO.write(scaledImage, "jpg", baos);
+// long cost = System.currentTimeMillis() - start;
+// System.out.println("处理耗时: " + cost + "ms");
+//
+// return new ByteArrayInputStream(baos.toByteArray());
+//
+// } catch (Exception e) {
+// throw new RuntimeException(e);
+// } finally {
+// if (grabber != null) {
+// try {
+// grabber.stop();
+// grabber.close();
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+// }
+// }
+ return null;
+ }
+ public ByteArrayInputStream generateThumbnail(
+ String videoFile,
+ int skipSeconds,
+ int targetWidth) {
+ String ffmpegPath = "";
+ if (osSelect.isWindows()) {
+ ffmpegPath = "ffmpeg";
+ } else if (osSelect.isLinux()) {
+ ffmpegPath = "/usr/local/ffmpeg/bin/ffmpeg";
+ }
try {
- grabber = new FFmpegFrameGrabber(videoFile);
- grabber.start();
+ List<String> command = new ArrayList<>();
+ command.add(ffmpegPath);
+ command.add("-ss");
+ command.add(String.format("00:00:%02d", skipSeconds));
+ command.add("-i");
+ command.add(videoFile);
+ command.add("-vframes");
+ command.add("1");
- int totalFrames = grabber.getLengthInFrames();
- System.out.println("视频总帧数: " + totalFrames);
- System.out.println("视频时长: " + grabber.getLengthInTime() / 1000000 + "秒");
+ // 等比缩放
+ command.add("-vf");
+ command.add("scale=" + targetWidth + ":-1");
- Frame frame = null;
- int currentFrame = 0;
+ // **确保输出 JPEG**
+ command.add("-f");
+ command.add("mjpeg"); // 关键点:输出格式固定为 JPEG
+ command.add("-vcodec");
+ command.add("mjpeg");
- // 读取指定帧数后的第一帧有效图像
- while (currentFrame < totalFrames) {
- frame = grabber.grabImage();
- if (frame == null) {
- break;
- }
+ // 输出到管道
+ command.add("pipe:1");
- if (currentFrame > skipFrames && frame.image != null) {
- break;
- }
- currentFrame++;
- }
+ // ⭐ 打印最终命令(可复制)
+ log.info("ffmpeg command: {}", String.join(" ", command));
- if (frame == null || frame.image == null) {
- throw new Exception("无法获取有效视频帧");
- }
+ ProcessBuilder pb = new ProcessBuilder(command);
+ pb.redirectErrorStream(false);
+ Process process = pb.start();
- // 转换 Frame 为 BufferedImage
- Java2DFrameConverter converter = new Java2DFrameConverter();
- BufferedImage image = converter.getBufferedImage(frame);
-
- // 按目标宽度等比缩放
- int targetHeight = (int) ((double) targetWidth / image.getWidth() * image.getHeight());
- BufferedImage scaledImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_3BYTE_BGR);
- scaledImage.getGraphics().drawImage(image, 0, 0, targetWidth, targetHeight, null);
-
- // 转成 ByteArrayInputStream
+ // 读取 FFmpeg 输出(JPEG 数据)到内存
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ImageIO.write(scaledImage, "jpg", baos);
- long cost = System.currentTimeMillis() - start;
- System.out.println("处理耗时: " + cost + "ms");
+ try (InputStream is = process.getInputStream()) {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = is.read(buffer)) != -1) {
+ baos.write(buffer, 0, len);
+ }
+ }
+
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ throw new RuntimeException("FFmpeg 执行失败,退出码: " + exitCode);
+ }
return new ByteArrayInputStream(baos.toByteArray());
- } catch (Exception e) {
- throw new RuntimeException(e);
- } finally {
- if (grabber != null) {
- try {
- grabber.stop();
- grabber.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("FFmpeg 生成缩略图失败", e);
}
}
diff --git a/ard-work/src/main/java/com/ard/work/util/osSelect.java b/ard-work/src/main/java/com/ard/work/util/osSelect.java
new file mode 100644
index 0000000..da5f288
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/util/osSelect.java
@@ -0,0 +1,39 @@
+package com.ard.work.util;
+
+/**
+ * 操作系统和架构判断工具类
+ * @author jiangxin
+ */
+public class osSelect {
+
+ private static final String OS_NAME = System.getProperty("os.name").toLowerCase();
+ private static final String OS_ARCH = System.getProperty("os.arch").toLowerCase(); // 例如:x86_64 / amd64 / aarch64
+
+ public static boolean isLinux() {
+ return OS_NAME.contains("linux");
+ }
+
+ public static boolean isWindows() {
+ return OS_NAME.contains("windows");
+ }
+
+ public static boolean isMac() {
+ return OS_NAME.contains("mac");
+ }
+
+ public static boolean isArm64() {
+ return OS_ARCH.contains("aarch64") || OS_ARCH.contains("arm64");
+ }
+
+ public static boolean isX64() {
+ return OS_ARCH.contains("x86_64") || OS_ARCH.contains("amd64");
+ }
+
+ public static String getArch() {
+ return OS_ARCH;
+ }
+
+ public static String getOsName() {
+ return OS_NAME;
+ }
+}
--
Gitblit v1.9.3