ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteFileService.java
@@ -34,8 +34,8 @@ * @return 访问地址 * @throws Exception */ @PostMapping(value = "upload/{bucketName}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file,@PathVariable("bucketName") String bucketName); @PostMapping(value = "/uploadWithBucket", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file,@RequestParam("bucketName") String bucketName); /** * 文件上传接口 @@ -46,7 +46,7 @@ * @return 访问地址 * @throws Exception */ @PostMapping(value = "uploadWithPath", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/uploadWithPath", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R<SysFile> upload(@RequestPart("file") MultipartFile file, @RequestParam("bucketName") String bucketName, @RequestParam("path") String path); /** ard-modules/ard-modules-file/pom.xml
@@ -65,6 +65,10 @@ <groupId>com.ard</groupId> <artifactId>ard-common-swagger</artifactId> </dependency> <dependency> <groupId>com.ard</groupId> <artifactId>ard-common-security</artifactId> </dependency> </dependencies> <build> ard-modules/ard-modules-file/src/main/java/com/ard/file/controller/SysFileController.java
@@ -5,9 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import com.ard.common.core.domain.R; import com.ard.common.core.utils.StringUtils; @@ -51,7 +49,39 @@ return R.fail(e.getMessage()); } } /** * 文件上传请求(带目录参数) * @param file 文件 * @param bucketName 桶名称 */ @PostMapping("uploadWithBucket") public R<SysFile> uploadWithBucket(@RequestPart("file")MultipartFile file, @RequestParam("bucketName") String bucketName) { try { // 上传并返回访问地址 String url = sysFileService.uploadFile(file, bucketName); SysFile sysFile = new SysFile(); sysFile.setName(FileUtils.getName(url)); sysFile.setUrl(url); return R.ok(sysFile); } catch (Exception e) { log.error("上传文件失败", e); return R.fail(e.getMessage()); } } @PostMapping("uploadWithPath") public R<SysFile> uploadWithPath(@RequestPart("file") MultipartFile file, @RequestParam("bucketName") String bucketName, @RequestParam("path") String path) { try { // 上传并返回访问地址 String url = sysFileService.uploadFile(file, bucketName, path); SysFile sysFile = new SysFile(); sysFile.setName(FileUtils.getName(url)); sysFile.setUrl(url); return R.ok(sysFile); } catch (Exception e) { log.error("上传文件失败", e); return R.fail(e.getMessage()); } } /** * 文件删除请求 */ ard-modules/ard-modules-file/src/main/java/com/ard/file/service/ISysFileService.java
@@ -17,7 +17,8 @@ * @throws Exception */ public String uploadFile(MultipartFile file) throws Exception; public String uploadFile(MultipartFile file, String bucketName)throws Exception; public String uploadFile(MultipartFile file, String bucketName, String path)throws Exception; /** * 文件删除接口 * ard-modules/ard-modules-file/src/main/java/com/ard/file/service/LocalSysFileServiceImpl.java
@@ -49,6 +49,16 @@ return url; } @Override public String uploadFile(MultipartFile file, String bucketName) throws Exception { return ""; } @Override public String uploadFile(MultipartFile file, String bucketName, String path) throws Exception { return ""; } /** * 本地文件删除接口 * ard-modules/ard-modules-file/src/main/java/com/ard/file/service/MinioSysFileServiceImpl.java
@@ -1,6 +1,9 @@ package com.ard.file.service; import java.io.InputStream; import io.minio.GetPresignedObjectUrlArgs; import io.minio.http.Method; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; @@ -20,8 +23,7 @@ */ @Primary @Service public class MinioSysFileServiceImpl implements ISysFileService { public class MinioSysFileServiceImpl implements ISysFileService { @Autowired private MinioConfig minioConfig; @@ -36,11 +38,9 @@ * @throws Exception */ @Override public String uploadFile(MultipartFile file) throws Exception { public String uploadFile(MultipartFile file) throws Exception { InputStream inputStream = null; try { try { String fileName = FileUploadUtils.extractFilename(file); inputStream = file.getInputStream(); PutObjectArgs args = PutObjectArgs.builder() @@ -51,33 +51,81 @@ .build(); client.putObject(args); return minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + fileName; } catch (Exception e) { } catch (Exception e) { throw new RuntimeException("Minio Failed to upload file", e); } finally { } finally { IoUtils.closeQuietly(inputStream); } } /** * Minio文件上传接口 * * @param file 上传的文件 * @param bucketName 桶名称 * @return 访问地址 * @author 刘苏义 * @description 指定桶名称上传文件 * @date 2024/8/5 11:37 */ @Override public String uploadFile(MultipartFile file, String bucketName) throws Exception { String fileName = FileUploadUtils.extractFilename(file); InputStream inputStream = file.getInputStream(); PutObjectArgs args = PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .stream(inputStream, file.getSize(), -1) .contentType(file.getContentType()) .build(); client.putObject(args); IoUtils.closeQuietly(inputStream); return minioConfig.getUrl() + "/" + bucketName + "/" + fileName; } @Override public String uploadFile(MultipartFile file, String bucketName, String path) throws Exception { //判断文件是否为空 if (null == file || 0 == file.getSize()) { return ""; } //文件名 String fileName = path + "/" + FileUploadUtils.extractFilename(file); InputStream inputStream = file.getInputStream(); /*上传对象*/ PutObjectArgs putObjectArgs = PutObjectArgs .builder() .bucket(bucketName) .object(fileName) .stream(inputStream, file.getSize(), -1) .contentType(file.getContentType()) .build(); client.putObject(putObjectArgs); inputStream.close(); /*获取url*/ GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs .builder() .bucket(bucketName) .object(fileName) .method(Method.GET) .build(); String presignedObjectUrl = client.getPresignedObjectUrl(getPresignedObjectUrlArgs); String ObjectUrl = presignedObjectUrl.substring(0, presignedObjectUrl.indexOf("?")); return ObjectUrl; } /** * Minio文件删除接口 * * * @param fileUrl 文件访问URL * @throws Exception */ @Override public void deleteFile(String fileUrl) throws Exception { try { String minioFile = StringUtils.substringAfter(fileUrl, minioConfig.getBucketName()+ "/"); public void deleteFile(String fileUrl) throws Exception { try { String minioFile = StringUtils.substringAfter(fileUrl, minioConfig.getBucketName() + "/"); client.removeObject(RemoveObjectArgs.builder().bucket(minioConfig.getBucketName()).object(minioFile).build()); } catch (Exception e) { } catch (Exception e) { throw new RuntimeException("Minio Failed to delete file", e); } } ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/aspect/SdkOperateAspect.java
@@ -69,6 +69,18 @@ else if (Boolean.class.isAssignableFrom(returnType) || boolean.class == returnType) { log.error(message); return false; }else if (String.class.isAssignableFrom(returnType)) { // 【新增】处理 String 返回类型 log.error(message); return ""; } else if (byte[].class.isAssignableFrom(returnType)) { // 【新增】处理 byte[] 返回类型 log.error(message); return new byte[0]; } else if (void.class == returnType || Void.class.isAssignableFrom(returnType)) { // 处理 void 返回类型 log.error(message); return null; } else { // 其他未知类型,为了安全还是返回 null,但最好记录错误 ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/dh/service/DaHuaSDK.java
@@ -5,6 +5,7 @@ import com.ard.common.core.exception.ServiceException; import com.ard.common.core.exception.device.CameraSDKException; import com.ard.common.core.utils.StringUtils; import com.ard.common.core.utils.file.FileMultipartFile; import com.ard.common.core.utils.file.FileUtils; import com.ard.common.core.utils.file.MimeTypeUtils; import com.ard.common.redis.service.RedisService; @@ -29,7 +30,6 @@ import com.ard.work.sdk.model.PtzScope; import com.ard.work.sdk.service.CameraSDK; import com.ard.work.utils.FFmpegUtils; import com.ard.work.utils.file.MultipartFileUtil; import com.ard.work.utils.gis.GisUtil; import com.sun.jna.Native; import com.sun.jna.Pointer; @@ -44,6 +44,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -675,10 +676,12 @@ log.warn("转码耗时:{}", watchStop.getTotalTimeMillis()); //存入minio String bucketName = "record"; String objectName = cameraId + "_" + chanNo; String fileName = cameraId + "_" + chanNo; String type = cmd.getType(); MultipartFile multipartFile = MultipartFileUtil.filePathToMultipartFile(newpath, objectName, MimeTypeUtils.VIDEO_MP4); // MultipartFile multipartFile = MultipartFileUtil.filePathToMultipartFile(newpath, objectName, // MimeTypeUtils.VIDEO_MP4); File tempFile = new File(newpath); MultipartFile multipartFile = new FileMultipartFile(tempFile, fileName, MimeTypeUtils.VIDEO_MP4); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); @@ -1182,17 +1185,49 @@ public void invoke(LLong lLoginID, Pointer pBuf, int RevLen, int EncodeType, int CmdSerial, Pointer dwUser) { if (pBuf != null && RevLen > 0) { byte[] buf = pBuf.getByteArray(0, RevLen); //存储到minio InputStream inputStream = new ByteArrayInputStream(buf); MultipartFile multipartFile = MultipartFileUtil.inputStreamToMultipartFile(inputStream, objectName, MimeTypeUtils.IMAGE_JPEG); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { String url = sysFileR.getData().getUrl(); log.info("上传文件成功:{}", url); // 在回调中完成 CompletableFuture urlFuture.complete(url); // 定义文件信息 String fileName = objectName; String tempDir = System.getProperty("java.io.tmpdir") + "/snap_temp/"; File tempDirFile = new File(tempDir); if (!tempDirFile.exists()) { tempDirFile.mkdirs(); } File tempFile = new File(tempDir + fileName); try { // 写入临时文件 try (FileOutputStream fos = new FileOutputStream(tempFile)) { fos.write(buf); fos.flush(); } // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, fileName, MimeTypeUtils.IMAGE_JPEG); // 上传到 MinIO R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { String url = sysFileR.getData().getUrl(); log.info("上传文件成功:{}", url); urlFuture.complete(url); } else { log.error("上传文件失败: {}", sysFileR.getMsg()); urlFuture.completeExceptionally(new RuntimeException("上传失败: " + sysFileR.getMsg())); } } catch (Exception e) { log.error("处理截图失败", e); urlFuture.completeExceptionally(e); } finally { // 清理临时文件 if (tempFile.exists()) { tempFile.delete(); } } } else { urlFuture.completeExceptionally(new RuntimeException("截图数据为空")); } } } ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/hik/service/AbstractHikVisionSDK.java
@@ -5,6 +5,7 @@ import com.ard.common.core.exception.ServiceException; import com.ard.common.core.exception.device.CameraSDKException; import com.ard.common.core.utils.StringUtils; import com.ard.common.core.utils.file.FileMultipartFile; import com.ard.common.core.utils.file.FileUtils; import com.ard.common.core.utils.file.MimeTypeUtils; import com.ard.common.redis.service.RedisService; @@ -30,7 +31,6 @@ import com.ard.work.sdk.model.PtzScope; import com.ard.work.sdk.service.CameraSDK; import com.ard.work.utils.FFmpegUtils; import com.ard.work.utils.file.MultipartFileUtil; import com.ard.work.utils.gis.GisUtil; import com.sun.jna.Native; import com.sun.jna.Pointer; @@ -1561,6 +1561,7 @@ String type = cmd.getType() == null ? "other" : cmd.getType(); String bucketName = cmd.getBucketName() == null ? "pic" : cmd.getBucketName(); int userId = cmd.getLoginId().intValue(); //图片信息 NET_DVR_JPEGPARA jpeg = new NET_DVR_JPEGPARA(); //设置图片分辨率 @@ -1570,8 +1571,9 @@ IntByReference a = new IntByReference(); //设置图片大小 ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024); // 抓图到内存,单帧数据捕获并保存成JPEG存放在指定的内存空间中 // 抓图到内存 boolean bool = hCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(userId, chanNo, jpeg, jpegBuffer, 1024 * 1024, a); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); @@ -1579,19 +1581,46 @@ log.warn(errorMsg); return ""; } log.debug("hikSdk(抓图)-结果状态值(0表示成功):{}", hCNetSDK.NET_DVR_GetLastError()); byte[] array = jpegBuffer.array(); //存储到minio String objectName = cameraId + "_" + chanNo + ".jpeg"; InputStream inputStream = new ByteArrayInputStream(array); String url = ""; MultipartFile multipartFile = MultipartFileUtil.inputStreamToMultipartFile(inputStream, objectName, MimeTypeUtils.IMAGE_JPEG); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); // 获取实际图片数据 int actualLength = a.getValue(); byte[] imageBytes; if (actualLength > 0 && actualLength < 1024 * 1024) { imageBytes = new byte[actualLength]; System.arraycopy(jpegBuffer.array(), 0, imageBytes, 0, actualLength); } else { imageBytes = jpegBuffer.array(); } // 存储到minio String objectName = cameraId + "_" + chanNo + ".jpeg"; String url = ""; File tempFile = null; try { // 创建临时文件 tempFile = File.createTempFile("snap_", ".jpeg"); java.nio.file.Files.write(tempFile.toPath(), imageBytes); // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, objectName, MimeTypeUtils.IMAGE_JPEG); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); } } catch (Exception e) { log.error("上传失败", e); } finally { // 删除临时文件 if (tempFile != null && tempFile.exists()) { tempFile.delete(); } } return url; } @@ -1602,6 +1631,7 @@ Integer chanNo = cmd.getChanNo(); String type = cmd.getType() == null ? "other" : cmd.getType(); Integer userId = cmd.getLoginId().intValue(); //图片信息 NET_DVR_JPEGPARA jpeg = new NET_DVR_JPEGPARA(); //设置图片分辨率 @@ -1613,6 +1643,7 @@ ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024); // 抓图到内存,单帧数据捕获并保存成JPEG存放在指定的内存空间中 boolean bool = hCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(userId, chanNo, jpeg, jpegBuffer, 1024 * 1024, a); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); @@ -1620,22 +1651,50 @@ log.warn(errorMsg); return ""; } byte[] array = jpegBuffer.array(); InputStream inputStream = new ByteArrayInputStream(array); String url = ""; MultipartFile multipartFile = MultipartFileUtil.inputStreamToMultipartFile(inputStream, objectName, MimeTypeUtils.IMAGE_JPEG); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); // 获取实际图片数据(只复制有效长度) int actualLength = a.getValue(); byte[] imageBytes; if (actualLength > 0 && actualLength < 1024 * 1024) { imageBytes = new byte[actualLength]; System.arraycopy(jpegBuffer.array(), 0, imageBytes, 0, actualLength); } else { imageBytes = jpegBuffer.array(); } String url = ""; File tempFile = null; try { // 创建临时文件 tempFile = File.createTempFile("snap_", ".jpeg"); java.nio.file.Files.write(tempFile.toPath(), imageBytes); // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, objectName, MimeTypeUtils.IMAGE_JPEG); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); } } catch (Exception e) { log.error("上传失败", e); } finally { if (tempFile != null && tempFile.exists()) { tempFile.delete(); } } return url; } //短时录像 @Override public String record(CameraCmd cmd) { String url = ""; File tempFile = null; try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); @@ -1644,29 +1703,32 @@ String path = FileUtils.createFile(tempDir + "/record/" + name + ".mp4"); boolean enable = cmd.isEnable(); Integer userId = cmd.getLoginId().intValue(); //强制I帧结构体对象 NET_DVR_I_FRAME netDvrIFrame = new NET_DVR_I_FRAME(); //新建结构体对象 NET_DVR_I_FRAME netDvrIFrame = new NET_DVR_I_FRAME(); netDvrIFrame.read(); netDvrIFrame.dwChannel = chanNo;//因为上文代码中设置了通道号,按照上文中的设置 netDvrIFrame.dwChannel = chanNo; netDvrIFrame.byStreamType = 0; netDvrIFrame.dwSize = netDvrIFrame.size(); netDvrIFrame.write(); if (!hCNetSDK.NET_DVR_RemoteControl(userId, 3402, netDvrIFrame.getPointer(), netDvrIFrame.dwSize)) { log.error("强制I帧 错误码为:{}", hCNetSDK.NET_DVR_GetLastError()); } //预览参数 NET_DVR_PREVIEWINFO previewInfo = new NET_DVR_PREVIEWINFO(); previewInfo.read(); previewInfo.lChannel = chanNo; previewInfo.dwStreamType = 0;//码流类型:0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推 previewInfo.dwLinkMode = 0;//连接方式:0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RTP/HTTP,6-HRUDP(可靠传输),7 // -RTSP/HTTPS,8-NPQ previewInfo.hPlayWnd = null;//播放窗口的句柄,为NULL表示不解码显示。 previewInfo.bBlocked = 0;//0- 非阻塞取流,1-阻塞取流 previewInfo.byNPQMode = 0;//NPQ模式:0-直连模式,1-过流媒体模式 previewInfo.dwStreamType = 0; previewInfo.dwLinkMode = 0; previewInfo.hPlayWnd = null; previewInfo.bBlocked = 0; previewInfo.byNPQMode = 0; previewInfo.write(); String url = ""; if (enable) { // 开始录像 if (!GlobalVariable.previewMap.containsKey(name)) { int lRealHandle = hCNetSDK.NET_DVR_RealPlay_V40(userId, previewInfo, null, null); if (lRealHandle == -1) { @@ -1676,6 +1738,7 @@ log.debug("取流成功"); GlobalVariable.previewMap.put(name, lRealHandle); } if (!hCNetSDK.NET_DVR_SaveRealData_V30((int) GlobalVariable.previewMap.get(name), 2, path)) { log.error("保存视频文件到临时文件夹失败 错误码为:{}", hCNetSDK.NET_DVR_GetLastError()); return ""; @@ -1683,24 +1746,40 @@ log.debug("录像开始"); } else { // 停止录像 if (GlobalVariable.previewMap.containsKey(name)) { Integer lRealHandle = (Integer) GlobalVariable.previewMap.get(name); hCNetSDK.NET_DVR_StopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); } log.debug("录像停止"); //存入minio // 检查录像文件是否存在 File videoFile = new File(path); if (!videoFile.exists() || videoFile.length() == 0) { log.error("录像文件不存在或为空: {}", path); return ""; } // 存入minio - 使用 FileMultipartFile String bucketName = "record"; String objectName = cameraId + "_" + chanNo; MultipartFile multipartFile = MultipartFileUtil.filePathToMultipartFile(path, objectName, MimeTypeUtils.VIDEO_MP4); String objectName = cameraId + "_" + chanNo + ".mp4"; // 创建临时文件对象(实际上 path 已经是文件路径) tempFile = new File(path); // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, objectName, MimeTypeUtils.VIDEO_MP4); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); } } return url; } catch (Exception ex) { log.error("录像异常:{}", ex.getMessage()); return ""; @@ -1763,69 +1842,81 @@ return false; } } @Override public String recordStop(CameraCmd cmd) { String url = ""; File videoFile = null; File transcodeFile = null; try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String name = cameraId + "_" + chanNo; // 本地临时录像地址 String path = FileUtils.createFile(tempDir + "/record/" + name + ".mp4"); Integer userId = cmd.getLoginId().intValue(); //region 强制I帧 NET_DVR_I_FRAME netDvrIFrame = new NET_DVR_I_FRAME(); //新建结构体对象 netDvrIFrame.read(); netDvrIFrame.dwChannel = chanNo;//因为上文代码中设置了通道号,按照上文中的设置 netDvrIFrame.byStreamType = 0; netDvrIFrame.dwSize = netDvrIFrame.size(); netDvrIFrame.write(); if (!hCNetSDK.NET_DVR_RemoteControl(userId, 3402, netDvrIFrame.getPointer(), netDvrIFrame.dwSize)) { log.error("强制I帧 错误码为:{}", hCNetSDK.NET_DVR_GetLastError()); } //endregion //region 预览参数 NET_DVR_PREVIEWINFO previewInfo = new NET_DVR_PREVIEWINFO(); previewInfo.read(); previewInfo.lChannel = chanNo; previewInfo.dwStreamType = 0;//码流类型:0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推 previewInfo.dwLinkMode = 0;//连接方式:0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RTP/HTTP,6-HRUDP(可靠传输),7 // -RTSP/HTTPS,8-NPQ previewInfo.hPlayWnd = null;//播放窗口的句柄,为NULL表示不解码显示。 previewInfo.bBlocked = 0;//0- 非阻塞取流,1-阻塞取流 previewInfo.byNPQMode = 0;//NPQ模式:0-直连模式,1-过流媒体模式 previewInfo.write(); //endregion // 停止录像 if (GlobalVariable.previewMap.containsKey(name)) { Integer lRealHandle = (Integer) GlobalVariable.previewMap.get(name); hCNetSDK.NET_DVR_StopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); } log.debug("录像停止"); //ffmpeg转码 // 检查原录像文件是否存在 videoFile = new File(path); if (!videoFile.exists() || videoFile.length() == 0) { log.error("录像文件不存在或为空: {}", path); return ""; } // ffmpeg转码 StopWatch watchStop = new StopWatch(); watchStop.start(); String newpath = FileUtils.createFile(tempDir + "/record/" + name + "_c.mp4"); transcodeFile = new File(newpath); FFmpegUtils.transcodeToMP4(path, newpath); watchStop.stop(); log.warn("转码耗时:{}", watchStop.getTotalTimeMillis()); //存入minio // 检查转码后的文件是否存在 if (!transcodeFile.exists() || transcodeFile.length() == 0) { log.error("转码文件不存在或为空: {}", newpath); return ""; } // 存入minio - 使用 FileMultipartFile String bucketName = "record"; String objectName = cameraId + "_" + chanNo; String objectName = cameraId + "_" + chanNo + ".mp4"; String type = cmd.getType(); MultipartFile multipartFile = MultipartFileUtil.filePathToMultipartFile(newpath, objectName, MimeTypeUtils.VIDEO_MP4); MultipartFile multipartFile = new FileMultipartFile(transcodeFile, objectName, MimeTypeUtils.VIDEO_MP4); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.info("上传文件成功:{}", url); } return url; } catch (Exception ex) { log.error("录像异常:{}", ex.getMessage()); return ""; } finally { // 清理临时文件 try { if (videoFile != null && videoFile.exists()) { videoFile.delete(); log.debug("删除原录像文件: {}", videoFile.getPath()); } if (transcodeFile != null && transcodeFile.exists()) { transcodeFile.delete(); log.debug("删除转码文件: {}", transcodeFile.getPath()); } } catch (Exception e) { log.warn("删除临时文件失败", e); } } } ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/hp/service/HpSDK.java
@@ -5,6 +5,7 @@ import com.ard.common.core.exception.ServiceException; import com.ard.common.core.exception.device.CameraSDKException; import com.ard.common.core.utils.StringUtils; import com.ard.common.core.utils.file.FileMultipartFile; import com.ard.common.core.utils.file.FileUtils; import com.ard.common.core.utils.file.MimeTypeUtils; import com.ard.common.redis.service.RedisService; @@ -25,7 +26,6 @@ import com.ard.work.sdk.model.PtzScope; import com.ard.work.utils.ArdTool; import com.ard.work.utils.FFmpegUtils; import com.ard.work.utils.file.MultipartFileUtil; import com.ard.work.utils.gis.GisUtil; import com.sun.jna.Pointer; import com.sun.jna.ptr.IntByReference; @@ -37,6 +37,7 @@ import org.springframework.util.StopWatch; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -1398,69 +1399,84 @@ private String hikRecordStop(CameraCmd cmd) { String url = ""; File videoFile = null; File transcodeFile = null; try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String name = cameraId + "_" + chanNo; // 本地临时录像地址 String path = FileUtils.createFile(tempDir + "/record/" + name + ".mp4"); Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId); if (userId == null) { throw new ServiceException("设备离线"); } //region 强制I帧 NET_DVR_I_FRAME netDvrIFrame = new NET_DVR_I_FRAME(); //新建结构体对象 netDvrIFrame.read(); netDvrIFrame.dwChannel = chanNo;//因为上文代码中设置了通道号,按照上文中的设置 netDvrIFrame.byStreamType = 0; netDvrIFrame.dwSize = netDvrIFrame.size(); netDvrIFrame.write(); if (!hCNetSDK.NET_DVR_RemoteControl(userId.intValue(), 3402, netDvrIFrame.getPointer(), netDvrIFrame.dwSize)) { log.error("强制I帧 错误码为:{}", hCNetSDK.NET_DVR_GetLastError()); } //endregion //region 预览参数 NET_DVR_PREVIEWINFO previewInfo = new NET_DVR_PREVIEWINFO(); previewInfo.read(); previewInfo.lChannel = chanNo; previewInfo.dwStreamType = 0;//码流类型:0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推 previewInfo.dwLinkMode = 0;//连接方式:0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RTP/HTTP,6-HRUDP(可靠传输),7 // -RTSP/HTTPS,8-NPQ previewInfo.hPlayWnd = null;//播放窗口的句柄,为NULL表示不解码显示。 previewInfo.bBlocked = 0;//0- 非阻塞取流,1-阻塞取流 previewInfo.byNPQMode = 0;//NPQ模式:0-直连模式,1-过流媒体模式 previewInfo.write(); //endregion // 停止录像 if (GlobalVariable.previewMap.containsKey(name)) { Integer lRealHandle = (Integer) GlobalVariable.previewMap.get(name); hCNetSDK.NET_DVR_StopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); } log.debug("录像停止"); //ffmpeg转码 // 检查原录像文件是否存在 videoFile = new File(path); if (!videoFile.exists() || videoFile.length() == 0) { log.error("录像文件不存在或为空: {}", path); return ""; } // ffmpeg转码 StopWatch watchStop = new StopWatch(); watchStop.start(); String newpath = FileUtils.createFile(tempDir + "/record/" + name + "_c.mp4"); transcodeFile = new File(newpath); FFmpegUtils.transcodeToMP4(path, newpath); watchStop.stop(); log.warn("转码耗时:{}", watchStop.getTotalTimeMillis()); //存入minio // 检查转码后的文件是否存在 if (!transcodeFile.exists() || transcodeFile.length() == 0) { log.error("转码文件不存在或为空: {}", newpath); return ""; } // 存入minio - 使用 FileMultipartFile String bucketName = "record"; String objectName = cameraId + "_" + chanNo; String type = cmd.getType(); MultipartFile multipartFile = MultipartFileUtil.filePathToMultipartFile(newpath, objectName, MimeTypeUtils.VIDEO_MP4); String objectName = cameraId + "_" + chanNo + ".mp4"; String type = cmd.getType() == null ? "record" : cmd.getType(); MultipartFile multipartFile = new FileMultipartFile(transcodeFile, objectName, MimeTypeUtils.VIDEO_MP4); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.info("上传文件成功:{}", url); } return url; } catch (Exception ex) { log.error("录像异常:{}", ex.getMessage()); return ""; } finally { // 清理临时文件 try { if (videoFile != null && videoFile.exists()) { videoFile.delete(); log.debug("删除原录像文件: {}", videoFile.getPath()); } if (transcodeFile != null && transcodeFile.exists()) { transcodeFile.delete(); log.debug("删除转码文件: {}", transcodeFile.getPath()); } } catch (Exception e) { log.warn("删除临时文件失败", e); } } } ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/zlxd/service/ZlxdSDK.java
@@ -6,6 +6,7 @@ import com.ard.common.core.exception.ServiceException; import com.ard.common.core.exception.device.CameraSDKException; import com.ard.common.core.utils.StringUtils; import com.ard.common.core.utils.file.FileMultipartFile; import com.ard.common.core.utils.file.MimeTypeUtils; import com.ard.common.redis.service.RedisService; import com.ard.system.api.RemoteFileService; @@ -27,7 +28,6 @@ import com.ard.work.sdk.zlxd.netty.tcp.NettyTcpClient; import com.ard.work.sdk.zlxd.utils.PTZ3DLocationConverter; import com.ard.work.sdk.zlxd.utils.XmlUtils; import com.ard.work.utils.file.MultipartFileUtil; import com.ard.work.utils.gis.GisUtil; import com.galaxy.sdk.RecordSdk; import com.galaxy.sdk.SDKConfig; @@ -774,17 +774,50 @@ log.warn("抓图失败,错误码:{},原因:{}", errorCode, errorDesc); return ""; } String base64Str = getRes.getParameters().getPicBuf(); //存储到minio if (base64Str == null || base64Str.isEmpty()) { log.warn("Base64图片数据为空"); return ""; } // 处理 Base64 前缀(如果有 data:image/jpeg;base64, 这样的前缀) String pureBase64 = base64Str; if (base64Str.contains(",")) { pureBase64 = base64Str.split(",")[1]; } // 存储到minio String objectName = cameraId + "_" + chanNo + ".jpeg"; String url = ""; MultipartFile multipartFile = MultipartFileUtil.base64ToMultipartFile(base64Str, objectName, MimeTypeUtils.IMAGE_JPEG); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); File tempFile = null; try { // Base64 解码 byte[] imageBytes = Base64.getDecoder().decode(pureBase64); // 创建临时文件 tempFile = File.createTempFile("snap_", ".jpeg"); java.nio.file.Files.write(tempFile.toPath(), imageBytes); // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, objectName, MimeTypeUtils.IMAGE_JPEG); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); } } catch (Exception e) { log.error("上传失败", e); } finally { // 删除临时文件 if (tempFile != null && tempFile.exists()) { tempFile.delete(); } } return url; } @@ -1075,18 +1108,20 @@ localFilePath = filePaths.get(0); log.info("录像停止成功,本地文件: {}", localFilePath); // 3. 上传到 MinIO // 校验文件是否存在 java.io.File file = new java.io.File(localFilePath); // 3. 校验文件是否存在 File file = new File(localFilePath); if (!file.exists()) { log.error("录像文件不存在,无法上传: {}", localFilePath); return ""; } MultipartFile multipartFile = MultipartFileUtil.filePathToMultipartFile( localFilePath, name, MediaType.APPLICATION_OCTET_STREAM_VALUE); // 建议使用具体的 MimeType // 4. 使用 FileMultipartFile 转换(不用 MultipartFileUtil) String objectName = cameraId + "_" + chanNo + ".mp4"; String type = cmd.getType() == null ? "record" : cmd.getType(); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, "record", cmd.getType()); MultipartFile multipartFile = new FileMultipartFile(file, objectName, MimeTypeUtils.VIDEO_MP4); R<SysFile> sysFileR = remoteFileService.upload(multipartFile, "record", type); if (sysFileR.getCode() == R.SUCCESS && sysFileR.getData() != null) { String url = sysFileR.getData().getUrl(); @@ -1099,12 +1134,9 @@ } catch (Exception ex) { log.error("停止录像过程发生异常: {}", name, ex); // 即使异常,SessionId 已经在第一步移除了,防止死锁 // 但这里可能需要记录日志以便人工排查残留文件 return ""; } finally { // 4. 【重要】清理本地临时文件 // 无论成功还是失败,只要生成了本地文件,上传完后都应该删除,防止磁盘爆满 // 5. 【重要】清理本地临时文件 if (localFilePath != null) { try { java.nio.file.Files.deleteIfExists(java.nio.file.Paths.get(localFilePath)); ard-modules/ard-modules-work/src/main/java/com/ard/work/utils/file/MultipartFileUtil.java
File was deleted ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java
@@ -8,10 +8,13 @@ import com.ard.common.core.domain.RtpServerParam; import com.ard.common.core.enums.LiveStreamType; import com.ard.common.core.utils.DateUtils; import com.ard.common.core.utils.file.FileMultipartFile; import com.ard.gb28181.api.RemoteGb28181Service; import com.ard.gb28181.api.domain.Device; import com.ard.qs.api.RemoteQsDeviceService; import com.ard.qs.api.domain.QsDevice; import com.ard.system.api.RemoteFileService; import com.ard.system.api.domain.SysFile; import com.ard.work.api.RemoteCameraService; import com.ard.work.api.RemoteChannelService; import com.ard.work.api.domian.ArdChannel; @@ -50,7 +53,12 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.DigestUtils; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.FileInputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; /** @@ -64,7 +72,8 @@ @Slf4j @Service public class MediaServerServiceImpl implements IMediaServerService { @Resource private RemoteFileService remoteFileService; @Resource private MediaServerMapper mediaServerMapper; @@ -787,6 +796,28 @@ * @param app app * @param stream 流id */ //@Override // public String snapOnPlay1(ZlmMediaServer mediaServer, String app, String stream) { // String fileName = app + "-" + stream + ".jpg"; // // 请求截图 // log.info("[请求截图]: " + fileName); // // IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); // if (mediaNodeServerService == null) { // log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); // throw new RuntimeException("[getSnap] 失败, mediaServer的类型: " + mediaServer.getType() + ",未找到对应的实现类"); // } // String filePath = fileDomain + filePrefix + "/snap/" + fileName; // mediaNodeServerService.getSnap(mediaServer, app, stream, 30, 300, this.filePath + "/snap", fileName); // return filePath; // } /** * 点播成功时调用截图 * * @param mediaServer media * @param app app * @param stream 流id */ @Override public String snapOnPlay(ZlmMediaServer mediaServer, String app, String stream) { String fileName = app + "-" + stream + ".jpg"; @@ -798,11 +829,53 @@ log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); throw new RuntimeException("[getSnap] 失败, mediaServer的类型: " + mediaServer.getType() + ",未找到对应的实现类"); } String filePath = fileDomain + filePrefix + "/snap/" + fileName; mediaNodeServerService.getSnap(mediaServer, app, stream, 30, 300, this.filePath + "/snap", fileName); return filePath; } // 生成临时文件路径(用于 FFmpeg 截图) String tempDir = System.getProperty("java.io.tmpdir") + "/snap_temp/"; File tempDirFile = new File(tempDir); if (!tempDirFile.exists()) { tempDirFile.mkdirs(); } String tempFilePath = tempDir + fileName; try { // 1. 获取截图到临时文件 mediaNodeServerService.getSnap(mediaServer, app, stream, 30, 300, tempDir, fileName); // 2. 检查临时文件是否存在 File tempFile = new File(tempFilePath); if (!tempFile.exists()) { log.error("[截图失败] 临时文件不存在: {}", tempFilePath); return null; } // 3. 使用 FileMultipartFile 转换为 MultipartFile MultipartFile multipartFile = new FileMultipartFile( tempFile, fileName, "image/jpeg" ); // 3. 调用文件上传接口,上传到 MinIO(bucketName 可以固定为 "snap" 或动态传入) R<SysFile> result = remoteFileService.upload(multipartFile, "snap"); // 4. 清理临时文件 Files.deleteIfExists(Paths.get(tempFilePath)); // 5. 返回 URL if (result.getCode() == Constants.SUCCESS && result.getData() != null) { String url = result.getData().getUrl(); log.info("[截图上传成功] URL: {}", url); return url; } else { log.error("[截图上传失败] {}", result.getMsg()); return null; } } catch (Exception e) { log.error("[截图失败] ", e); return null; } } /** * 获取截图 * ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/utils/ZLMRESTfulUtils.java
@@ -656,7 +656,7 @@ param.put("url", streamUrl); param.put("timeout_sec", timeout_sec); param.put("expire_sec", expire_sec); param.put("async", 1); param.put("async", 0); sendGetForImg(mediaServer, "getSnap", param, targetPath, fileName); }