liusuyi
2026-05-14 9958bc1501d0daee954d9bba383475e55e24ebab
优化
1 files deleted
13 files modified
749 ■■■■■ changed files
ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteFileService.java 6 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-file/pom.xml 4 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-file/src/main/java/com/ard/file/controller/SysFileController.java 38 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-file/src/main/java/com/ard/file/service/ISysFileService.java 3 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-file/src/main/java/com/ard/file/service/LocalSysFileServiceImpl.java 10 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-file/src/main/java/com/ard/file/service/MinioSysFileServiceImpl.java 86 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/aspect/SdkOperateAspect.java 12 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/dh/service/DaHuaSDK.java 53 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/hik/service/AbstractHikVisionSDK.java 195 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/hp/service/HpSDK.java 76 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-work/src/main/java/com/ard/work/sdk/zlxd/service/ZlxdSDK.java 58 ●●●● patch | view | raw | blame | history
ard-modules/ard-modules-work/src/main/java/com/ard/work/utils/file/MultipartFileUtil.java 125 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java 81 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/utils/ZLMRESTfulUtils.java 2 ●●● patch | view | raw | blame | history
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,15 +51,67 @@
                    .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;
    }
    /**
@@ -69,15 +121,11 @@
     * @throws Exception
     */
    @Override
    public void deleteFile(String fileUrl) throws Exception
    {
        try
        {
    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);
                // 定义文件信息
                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);
                    // 在回调中完成 CompletableFuture
                    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();
        // 获取实际图片数据
        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";
        InputStream inputStream = new ByteArrayInputStream(array);
        String url = "";
        MultipartFile multipartFile = MultipartFileUtil.inputStreamToMultipartFile(inputStream, objectName,
                MimeTypeUtils.IMAGE_JPEG);
        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);
        // 获取实际图片数据(只复制有效长度)
        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 = "";
        MultipartFile multipartFile = MultipartFileUtil.inputStreamToMultipartFile(inputStream, objectName,
                MimeTypeUtils.IMAGE_JPEG);
        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("录像停止");
            // 检查原录像文件是否存在
            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("录像停止");
            // 检查原录像文件是否存在
            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();
        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);
        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);
    }