From 1e9e8153141f03c6bda5f0ba52d49d92729b25cf Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Fri, 21 Nov 2025 16:24:07 +0800 Subject: [PATCH 1/2] uploadVideo --- .../app/controller/AppVideoController.java | 25 +++-- .../fuyuanshen/web/util/VideoProcessUtil.java | 30 ++++++ .../controller/DeviceController.java | 3 - .../converter/IgnoreFailedImageConverter.java | 98 ++++++++++++++++--- .../service/impl/DeviceServiceImpl.java | 12 +-- 5 files changed, 139 insertions(+), 29 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index f48a1a0a..8e40e400 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -6,6 +6,7 @@ import com.fuyuanshen.app.service.VideoProcessService; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.utils.FileHashUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; @@ -28,18 +29,30 @@ public class AppVideoController extends BaseController { private final VideoProcessService videoProcessService; private final AudioProcessService audioProcessService; + private final FileHashUtil fileHashUtil; + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") - public R> uploadVideo(@RequestParam("file") MultipartFile file) { + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") + public R> uploadVideo(@RequestParam("file") MultipartFile file) throws IOException { + // 输出文件基本信息 + System.out.println("FileName: " + file.getOriginalFilename()); + System.out.println("FileSize: " + file.getSize()); + System.out.println("ContentType: " + file.getContentType()); + + String fileHash = fileHashUtil.hash(file); + System.out.println("fileHash:" + fileHash); + + // 可以添加更多视频属性检查 return R.ok(videoProcessService.processVideo(file)); } + /** * 上传音频文件并转码 */ @PostMapping(value = "/audio", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") public R> uploadAudio(@RequestParam("file") MultipartFile file) { return R.ok(audioProcessService.processAudio(file)); } @@ -48,7 +61,7 @@ public class AppVideoController extends BaseController { * 文字转音频TTS服务 */ @GetMapping("/audioTTS") - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") public R> uploadAudioTTS(@RequestParam String text) throws IOException { return R.ok(audioProcessService.generateStandardPcmData(text)); } @@ -57,8 +70,8 @@ public class AppVideoController extends BaseController { * 提取文本内容(只支持txt/docx) */ @PostMapping(value = "/extract", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "请勿重复提交!") public R extract(@RequestParam("file") MultipartFile file) throws Exception { - return R.ok("Success",audioProcessService.extract(file)); + return R.ok("Success", audioProcessService.extract(file)); } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java index 06f96d5e..206342d1 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java @@ -48,52 +48,82 @@ public class VideoProcessUtil { return bytesToHexList(binaryData); } + /** * 从视频中提取帧 + * + * @param videoFile 视频文件对象 + * @param frameRate 每秒提取的帧数(帧率) + * @param duration 需要提取的视频时长(秒) + * @param width 提取帧的宽度 + * @param height 提取帧的高度 + * @return 提取的帧图像列表 + * @throws Exception 如果在提取过程中发生错误 */ private List extractFramesFromVideo(File videoFile, int frameRate, int duration, int width, int height) throws Exception { + // 初始化帧列表 List frames = new ArrayList<>(); + // 计算需要提取的总帧数 = 帧率 × 时长 int totalFramesToExtract = frameRate * duration; + // 使用FFmpegFrameGrabber从视频文件中抓取帧 try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) { + // 启动抓取器 grabber.start(); + // 获取视频总帧数 long totalFramesInVideo = grabber.getLengthInFrames(); + // 获取视频帧率,如果获取不到则默认为30fps int fps = (int) Math.round(grabber.getFrameRate()); if (fps <= 0) fps = 30; + // 计算视频总时长(秒) double durationSeconds = (double) totalFramesInVideo / fps; + // 检查视频时长是否满足要求 if (durationSeconds < duration) { throw new IllegalArgumentException("视频太短,至少需要 " + duration + " 秒"); } + // 计算帧间隔,用于均匀分布提取的帧 double frameInterval = (double) totalFramesInVideo / totalFramesToExtract; + // 循环提取指定数量的帧 for (int i = 0; i < totalFramesToExtract; i++) { + // 计算目标帧号 int targetFrameNumber = (int) Math.round(i * frameInterval); + // 检查目标帧号是否超出视频范围 if (targetFrameNumber >= totalFramesInVideo) { throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber); } + // 设置抓取器到目标帧 grabber.setFrameNumber(targetFrameNumber); + // 抓取当前帧 Frame frame = grabber.grab(); + // 如果成功抓取到帧且帧图像不为空 if (frame != null && frame.image != null) { + // 将帧转换为BufferedImage并裁剪到指定尺寸 BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame); frames.add(cropImage(bufferedImage, width, height)); } else { + // 如果无法获取帧则抛出异常 throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "帧"); } } + // 停止抓取器 grabber.stop(); } + // 记录提取的帧数 log.debug("从视频中提取了 {} 帧", frames.size()); + // 返回提取的帧列表 return frames; } + /** * 将所有帧转换为 RGB565 格式字节数组 */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java index bd6f0c15..991c04a9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java @@ -3,9 +3,7 @@ package com.fuyuanshen.equipment.controller; import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.fuyuanshen.common.core.constant.ResponseMessageConstants; import com.fuyuanshen.common.core.domain.R; -import com.fuyuanshen.common.core.domain.ResponseVO; import com.fuyuanshen.common.core.domain.model.LoginUser; import com.fuyuanshen.common.core.utils.file.FileUtil; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @@ -18,7 +16,6 @@ import com.fuyuanshen.equipment.domain.dto.DeviceExcelImportDTO; import com.fuyuanshen.equipment.domain.dto.ImportResult; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; -import com.fuyuanshen.equipment.domain.vo.CustomerVo; import com.fuyuanshen.equipment.excel.DeviceImportParams; import com.fuyuanshen.equipment.excel.HeadValidateListener; import com.fuyuanshen.equipment.excel.UploadDeviceDataListener; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java index 60c62d98..1ecd4db8 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java @@ -17,6 +17,10 @@ import java.net.URLConnection; import java.util.Base64; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * @author: 默苍璃 @@ -41,12 +45,15 @@ public class IgnoreFailedImageConverter implements Converter { } }; + // 创建线程池用于并发处理图片 + private static final ExecutorService IMAGE_PROCESSING_EXECUTOR = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors() * 2); + @Override public Class supportJavaTypeKey() { return URL.class; } - @Override public WriteCellData convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (value == null) { @@ -54,11 +61,36 @@ public class IgnoreFailedImageConverter implements Converter { return new WriteCellData<>(new byte[0]); } + try { + // 使用CompletableFuture异步处理图片加载 + CompletableFuture> future = CompletableFuture.supplyAsync(() -> { + try { + return loadImageData(value); + } catch (Exception e) { + logger.error("异步加载图片失败: {}", value, e); + return new WriteCellData<>(new byte[0]); + } + }, IMAGE_PROCESSING_EXECUTOR); + + // 设置超时时间,防止长时间阻塞 + return future.get(30, TimeUnit.SECONDS); + } catch (Exception e) { + logger.error("图片处理异常: {}", value, e); + return new WriteCellData<>(new byte[0]); + } + } + + /** + * 加载图片数据的核心方法 + * @param value 图片URL + * @return WriteCellData对象 + */ + private WriteCellData loadImageData(URL value) { String cacheKey = "excel:image:" + value.toString(); - + // 将当前使用的缓存键添加到集合中 USED_CACHE_KEYS.get().add(cacheKey); - + // 尝试从缓存获取 String cachedData = RedisUtils.getCacheObject(cacheKey); if (cachedData != null) { @@ -75,11 +107,13 @@ public class IgnoreFailedImageConverter implements Converter { logger.info("开始加载图片: {}, 尝试次数: {}", value, attempt); URLConnection conn = value.openConnection(); // 增加连接和读取超时时间 - conn.setConnectTimeout(10000); // 10秒连接超时 - conn.setReadTimeout(30000); // 30秒读取超时 + conn.setConnectTimeout(5000); // 5秒连接超时 + conn.setReadTimeout(15000); // 15秒读取超时 // 添加User-Agent避免被服务器拦截 conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ExcelExporter/1.0"); + // 添加Connection: close避免保持连接 + conn.setRequestProperty("Connection", "close"); // 如果是HTTP连接,设置一些额外的属性 if (conn instanceof HttpURLConnection) { @@ -152,11 +186,20 @@ public class IgnoreFailedImageConverter implements Converter { if (bytes.length > COMPRESSION_THRESHOLD) { logger.info("图片大小超过1MB ({} bytes),开始压缩", bytes.length); long beforeCompressSize = bytes.length; - bytes = ImageCompressUtil.compressImage(bytes, COMPRESSION_TARGET); + + // 先尝试质量压缩 + byte[] compressed = ImageCompressUtil.compressImage(bytes, COMPRESSION_TARGET); + + // 如果压缩后变大了,使用原始数据 + if (compressed.length >= bytes.length) { + compressed = bytes; + } + + bytes = compressed; long afterCompressSize = bytes.length; - logger.info("图片压缩完成,压缩前大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%", - beforeCompressSize, afterCompressSize, - String.format("%.2f", (1.0 - (double) afterCompressSize / beforeCompressSize) * 100)); + logger.info("图片压缩完成,压缩前大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}", + beforeCompressSize, afterCompressSize, + String.format("%.2f", (1.0 - (double) afterCompressSize / beforeCompressSize) * 100)); } logger.info("成功读取图片数据,大小: {} 字节", bytes.length); @@ -209,7 +252,6 @@ public class IgnoreFailedImageConverter implements Converter { } } - /** * 等待重试,使用指数退避策略 * @@ -225,7 +267,6 @@ public class IgnoreFailedImageConverter implements Converter { } } - /** * 替代 FileUtils.readInputStream 的自定义方法 * @@ -253,4 +294,37 @@ public class IgnoreFailedImageConverter implements Converter { return outputStream.toByteArray(); } -} \ No newline at end of file + /** + * 预加载图片到缓存 + * @param imageUrls 图片URL列表 + */ + public static void preloadImages(Set imageUrls) { + if (imageUrls == null || imageUrls.isEmpty()) { + return; + } + + logger.info("开始预加载 {} 张图片", imageUrls.size()); + + // 使用并行流并发预加载图片 + imageUrls.parallelStream().forEach(url -> { + try { + String cacheKey = "excel:image:" + url.toString(); + // 如果缓存中没有,则异步加载 + if (!RedisUtils.hasKey(cacheKey)) { + CompletableFuture.runAsync(() -> { + try { + // 简化版图片加载逻辑,只加载到缓存 + new IgnoreFailedImageConverter().loadImageData(url); + } catch (Exception e) { + logger.warn("预加载图片失败: {}", url, e); + } + }, IMAGE_PROCESSING_EXECUTOR); + } + } catch (Exception e) { + logger.warn("预加载图片异常: {}", url, e); + } + }); + + logger.info("图片预加载任务已提交"); + } +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 78a83f51..533b06d0 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -19,11 +19,8 @@ import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.customer.domain.Customer; import com.fuyuanshen.customer.mapper.CustomerMapper; -import com.fuyuanshen.equipment.constants.DeviceConstants; import com.fuyuanshen.equipment.domain.*; -import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; -import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceAssignmentQuery; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; @@ -33,7 +30,10 @@ import com.fuyuanshen.equipment.enums.BindingStatusEnum; import com.fuyuanshen.equipment.enums.CommunicationModeEnum; import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum; import com.fuyuanshen.equipment.mapper.*; -import com.fuyuanshen.equipment.service.*; +import com.fuyuanshen.equipment.service.DeviceAssignmentsService; +import com.fuyuanshen.equipment.service.DeviceService; +import com.fuyuanshen.equipment.service.DeviceTypeGrantsService; +import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; import com.fuyuanshen.equipment.utils.FileHashUtil; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysRoleVo; @@ -41,15 +41,11 @@ import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysRoleService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import java.io.File; import java.io.IOException; import java.sql.Timestamp; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; From bf182ebc89d3fefc31c41f6a2185de0eb021a4f9 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 25 Nov 2025 14:51:10 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=BC=BA=E5=88=B6=E5=B0=86HTTP=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E4=B8=BAHTTPS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fuyuanshen/app/controller/AppVideoController.java | 3 +-- .../equipment/domain/dto/DeviceWithTypeExcelExportDTO.java | 2 +- .../fuyuanshen/equipment/service/impl/DeviceServiceImpl.java | 4 ++++ .../src/main/java/com/fuyuanshen/system/domain/SysOss.java | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index 8e40e400..9dbb01d6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -1,6 +1,5 @@ package com.fuyuanshen.app.controller; -import cn.dev33.satoken.annotation.SaIgnore; import com.fuyuanshen.app.service.AudioProcessService; import com.fuyuanshen.app.service.VideoProcessService; import com.fuyuanshen.common.core.domain.R; @@ -14,7 +13,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.Base64; import java.util.List; import java.util.concurrent.TimeUnit; @@ -74,4 +72,5 @@ public class AppVideoController extends BaseController { public R extract(@RequestParam("file") MultipartFile file) throws Exception { return R.ok("Success", audioProcessService.extract(file)); } + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java index 0ffced9e..00f638e9 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java @@ -13,7 +13,7 @@ import java.net.URL; * 设备及完整类型信息导出DTO * * @author: 默苍璃 - * @date: 2025-11-0416:25 + * @date: 2025-11-04 16:25 */ @Data @HeadRowHeight(20) // 表头行高 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 533b06d0..da9372dc 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -286,6 +286,10 @@ public class DeviceServiceImpl extends ServiceImpl impleme if (deviceForm.getFile() != null) { String fileHash = fileHashUtil.hash(deviceForm.getFile()); SysOssVo upload = ossService.updateHash(deviceForm.getFile(), fileHash); + // 强制将HTTP替换为HTTPS + if (upload.getUrl() != null && upload.getUrl().startsWith("http://")) { + upload.setUrl(upload.getUrl().replaceFirst("^http://", "https://")); + } // 设置图片路径 deviceForm.setDevicePic(upload.getUrl()); } diff --git a/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java b/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java index 6d187f57..99e941f6 100644 --- a/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java +++ b/fys-modules/fys-system/src/main/java/com/fuyuanshen/system/domain/SysOss.java @@ -51,6 +51,7 @@ public class SysOss extends TenantEntity { * 服务商 */ private String service; + /** * 内容哈希 */