package com.ard.quartz.task; import com.ard.common.constant.CacheConstants; import com.ard.common.core.redis.RedisCache; import com.ard.common.utils.DateUtils; import com.ard.common.utils.file.MimeTypeUtils; import com.ard.device.camera.domain.ArdCamera; import com.ard.device.camera.service.IArdCameraService; import com.ard.device.channel.domain.ArdChannel; import com.ard.device.channel.service.IArdChannelService; import com.ard.device.component.sdk.service.CameraSDKService; import com.ard.oss.service.impl.OssServiceContext; import com.ard.vtdu.config.MediamtxConfig; import com.ard.vtdu.domain.ArdVtdu; import com.ard.vtdu.service.IArdMediaService; import com.ard.vtdu.service.IArdVtduService; import com.ard.work.domain.entity.ArdMediaPic; import com.ard.work.domain.entity.ArdMediaVideo; import com.ard.work.mapper.ArdMediaPicMapper; import com.ard.work.mapper.ArdMediaVideoMapper; import com.ard.work.util.osSelect; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.security.cert.X509Certificate; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; /** * 定时同步设备任务 * * @author ard */ @Component("DeviceTask") @Slf4j public class DeviceTask { @Resource private RedisCache redisCache; @Resource private IArdChannelService ardChannelService; @Resource private IArdVtduService ardVtduService; @Resource private MediamtxConfig mediamtxConfig; @Resource private CameraSDKService cameraSDKService; @Resource private IArdCameraService ardCameraService; @Resource private IArdMediaService ardMediaService; /** * 定时执行登录失败相机重新登录 */ public void failedReconnect() { try { log.debug("定时执行失败相机重连任务"); // 从数据库取全部设备 ArdCamera ardCamera = new ArdCamera(); ardCamera.setUseDataScope(false); List cameraList = ardCameraService.selectArdCameraList(ardCamera); cameraList.stream() // 只处理登录失败或离线的设备 .filter(camera -> camera.getLoginId() == null || camera.getLoginId() < 0 || "0".equals(camera.getState())) .forEach(camera -> { log.info("尝试重新登录设备 {}:{}", camera.getIp(), camera.getPort()); cameraSDKService.login(camera); }); } catch (Exception ex) { log.error("定时执行失败相机重连任务异常:{}", ex.getMessage()); } } /** * 同步相机在线状态 */ public void syncCameraState() { try { log.debug("定时执行同步相机在线状态任务"); //调用查询方法 ArdCamera ardCamera = new ArdCamera(); ardCamera.setUseDataScope(false); List ardCameraList = ardCameraService.selectArdCameraList(ardCamera); ardCameraList .forEach(camera -> { boolean onLineNew = cameraSDKService.isOnLine(camera); boolean onLineOld = "1".equals(camera.getState()); if (onLineNew != onLineOld) { if (onLineNew) { camera.setState("1"); } else { camera.setState("0"); } camera.setUpdateTime(DateUtils.getNowDate()); ardCameraService.updateArdCamera(camera); } }); } catch (Exception ex) { log.error("同步相机在线状态异常:{}", ex.getMessage()); } } /** * 同步相机通道 */ public void syncChannel() { try { log.debug("定时执行同步通道任务"); ArdCamera camera = new ArdCamera(); camera.setUseDataScope(false); List ardCameraList = ardCameraService.selectArdCameraList(camera); if (!ardCameraList.isEmpty()) { ardCameraList .forEach(ardCamera -> { //SDK获取实际通道 List ardChannelListSDK = cameraSDKService.getChannels(ardCamera); //数据库获取通道 ArdChannel ardChannel = new ArdChannel(); ardChannel.setUseDataScope(false); ardChannel.setDeviceId(ardCamera.getId()); List ardChannelListDb = ardChannelService.selectArdChannelList(ardChannel); //同步通道 ardCameraService.asyncChannel(ardChannelListDb, ardChannelListSDK); }); } } catch (Exception ex) { log.error("定时执行同步通道任务异常:{}", ex.getMessage()); } } /** * 同步通道流媒体 */ public void syncVtdu() { log.debug("定时同步Vtdu任务"); ArdChannel ardChannel = new ArdChannel(); ardChannel.setUseDataScope(false); List channels = ardChannelService.selectArdChannelList(ardChannel); if (channels == null || channels.isEmpty()) { // log.warn("没有获取到通道数据,退出同步"); return; } List ardVtduList = ardVtduService.selectArdVtduList(new ArdVtdu()); Set validNames = channels.stream() .map(channel -> channel.getDeviceId() + "_" + channel.getChanNo()) .collect(Collectors.toSet()); // 2. 合并操作:增、删、改 channels.forEach(channel -> { String name = channel.getDeviceId() + "_" + channel.getChanNo(); // 生成媒体表的 name String cameraId = channel.getDeviceId(); // cameraId 就是通道的 deviceId String rtspSourceUrl = getRtspSource(channel); String rtspUrl = "rtsp://" + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getRtspPort() + "/" + name; // // 拼接 webrtcUrl String rtmpUrl = "rtmp://" + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getRtmpPort() + "/" + name; // 拼接 // webrtcUrl String protocol = mediamtxConfig.isHttps() ? "https://" : "http://"; String webrtcUrl = protocol + mediamtxConfig.getPlayHost() + ":" + mediamtxConfig.getWebrtcPort() + "/" + name; // 拼接 webrtcUrl // 先检查媒体表是否已有此记录 Optional existingMedia = ardVtduList.stream() .filter(media -> media.getName().equals(name)) // 查找是否已有对应的媒体记录 .findFirst(); if (existingMedia.isPresent()) { // 如果已有,更新现有记录 ArdVtdu vtdu = existingMedia.get(); // 更新前后内容相同则不进行更新 if (!vtdu.getRtspSource().equals(rtspSourceUrl) || !vtdu.getWebrtcUrl().equals(webrtcUrl) || !vtdu.getRtspUrl().equals(rtspUrl) || !vtdu.getRtmpUrl().equals(rtmpUrl)) { vtdu.setRtspUrl(rtspUrl); vtdu.setRtmpUrl(rtmpUrl); vtdu.setWebrtcUrl(webrtcUrl); ardVtduService.updateArdVtdu(vtdu); log.info("更新媒体记录:{}", vtdu.getName()); } } else { // 如果没有,插入新的记录 ArdVtdu vtdu = new ArdVtdu(); vtdu.setName(name); vtdu.setRtspSource(rtspSourceUrl); vtdu.setCameraId(cameraId); vtdu.setRtspUrl(rtspUrl); vtdu.setRtmpUrl(rtmpUrl); vtdu.setWebrtcUrl(webrtcUrl); ardVtduService.insertArdVtdu(vtdu); // 保存到媒体表 log.info("插入新媒体记录:{}", vtdu.getName()); } }); // 3. 删除操作:删除媒体表中多余的记录 List invalidNames = ardVtduList.stream() .filter(vtdu -> !validNames.contains(vtdu.getName())) .filter(vtdu -> !"publisher".equalsIgnoreCase(vtdu.getRtspSource())) // 不是机场流 .map(ArdVtdu::getName) .collect(Collectors.toList()); if (!invalidNames.isEmpty()) { // 将 List 转换为 String[] String[] namesArray = invalidNames.toArray(new String[0]); ardVtduService.deleteArdVtduByNames(namesArray); // 传递数组 log.info("删除不再存在的媒体记录:{}", Arrays.toString(namesArray)); } } // 根据通道信息获取rtsp源地址 private String getRtspSource(ArdChannel channel) { String rtspSource = ""; ArdCamera ardCamera = redisCache.getCacheObject(CacheConstants.CAMERA_LIST_KEY + channel.getDeviceId()); if (ardCamera != null) { switch (ardCamera.getFactory()) { case "1": rtspSource = "rtsp://" + ardCamera.getUsername() + ":" + ardCamera.getPassword() + "@" + ardCamera.getIp() + ":" + ardCamera.getRtspPort() + "/h264/ch" + channel.getChanNo() + "/main/av_stream"; break; case "2": rtspSource = "rtsp://" + ardCamera.getUsername() + ":" + ardCamera.getPassword() + "@" + ardCamera.getIp() + ":" + ardCamera.getRtspPort() + "/cam/realmonitor?channel=" + channel.getChanNo() + "&subtype=0"; break; } } return rtspSource; } /** * 同步mediamtx */ public void syncMediamtx() { log.debug("定时同步MediaMTX任务"); List nameList = ardMediaService.getNameList(); List ardVtduList = ardVtduService.selectArdVtduList(new ArdVtdu()); ardVtduService.asyncMediaMTX(ardVtduList, nameList); } }