ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/RemoteGb28181Service.java
@@ -90,6 +90,12 @@ R<List<GbDeviceDTO>> getAllGbDevices(@RequestHeader(SecurityConstants.FROM_SOURCE) String inner); /** * 根据主键ID获取GbChannel */ @GetMapping("/api/gb28181/getGbChannelById/{id}") R<GbChannelDTO> getGbChannelById(@PathVariable Long id, @RequestHeader(SecurityConstants.FROM_SOURCE) String inner); /** * 根据国标设备ID和通道ID获取GbChannel */ @GetMapping("/api/gb28181/getGbChannel/{gbDeviceId}/{gbChannelId}") ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/factory/RemoteGb28181FallbackFactory.java
@@ -67,6 +67,11 @@ } @Override public R<GbChannelDTO> getGbChannelById(Long id, String inner) { return R.fail("gb28181 根据主键ID获取GbChannel失败:" + throwable.getMessage()); } @Override public R<GbChannelDTO> getGbChannel(String gbDeviceId, String gbChannelId, String inner) { return R.fail("gb28181 获取GbChannel失败:" + throwable.getMessage()); } ard-modules/ard-modules-gb28181/pom.xml
@@ -114,12 +114,6 @@ <version>5.8.38</version> </dependency> <!-- 泉视 接口 --> <dependency> <groupId>com.ard</groupId> <artifactId>ard-api-qs</artifactId> </dependency> <!-- gb28181 接口 --> <dependency> <groupId>com.ard</groupId> ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/api/Gb28181ApiController.java
@@ -5,10 +5,7 @@ import com.ard.common.core.domain.R; import com.ard.common.core.domain.RtpServerParam; import com.ard.common.core.exception.ServiceException; import com.ard.gb28181.api.domain.Device; import com.ard.gb28181.api.domain.DeviceChannel; import com.ard.gb28181.api.domain.GbChannelDTO; import com.ard.gb28181.api.domain.GbDeviceDTO; import com.ard.gb28181.api.domain.*; import com.ard.gb28181.config.UserSetting; import com.ard.gb28181.domain.GbChannel; import com.ard.gb28181.domain.GbDevice; @@ -24,12 +21,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; import javax.sip.ResponseEvent; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -728,6 +728,18 @@ } /** * 根据主键ID获取GbChannel */ @GetMapping("/getGbChannelById/{id}") public R<GbChannelDTO> getGbChannelById(@PathVariable Long id) { GbChannel gbChannel = gbChannelService.selectGbChannelById(id); if (gbChannel == null) { return R.fail("GbChannel不存在 id:" + id); } return R.ok(toGbChannelDTO(gbChannel)); } /** * 根据国标设备ID和通道ID获取GbChannel */ @GetMapping("/getGbChannel/{gbDeviceId}/{gbChannelId}") @@ -773,12 +785,12 @@ dto.setGbDeviceId(d.getGbDeviceId()); dto.setDeviceName(d.getDeviceName()); dto.setDeviceCode(d.getDeviceCode()); dto.setStreamMode(d.getStreamMode()); dto.setEnableMp4(d.getEnableMp4()); dto.setStreamStatus(d.getStreamStatus()); dto.setMediaServerId(d.getMediaServerId()); dto.setStreamKey(d.getStreamKey()); dto.setSnap(d.getSnap()); dto.setIp(d.getIp()); dto.setPort(d.getPort()); dto.setManufacturer(d.getManufacturer()); dto.setModel(d.getModel()); dto.setFirmware(d.getFirmware()); dto.setOnLine(d.getOnLine()); return dto; } @@ -815,10 +827,14 @@ if (existingDevice == null) { GbDevice gbDevice = new GbDevice(); gbDevice.setGbDeviceId(device.getDeviceId()); gbDevice.setDeviceName(device.getName() != null ? device.getName() : device.getDeviceId()); gbDevice.setDeviceName(StringUtils.hasText(device.getName()) ? device.getName() : device.getDeviceId()); gbDevice.setDeviceCode(device.getDeviceId()); gbDevice.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE"); gbDevice.setEnableMp4("0"); gbDevice.setIp(device.getIp()); gbDevice.setPort(device.getPort()); gbDevice.setManufacturer(device.getManufacturer()); gbDevice.setModel(device.getModel()); gbDevice.setFirmware(device.getFirmware()); gbDevice.setOnLine(device.isOnLine()); gbDeviceService.insertGbDevice(gbDevice); deviceCount++; log.info("[迁移] 创建设备: {}", device.getDeviceId()); @@ -831,7 +847,7 @@ GbChannel gbChannel = new GbChannel(); gbChannel.setGbDeviceId(device.getDeviceId()); gbChannel.setGbChannelId(device.getDeviceId()); gbChannel.setChannelName(device.getName() != null ? device.getName() : device.getDeviceId()); gbChannel.setChannelName(StringUtils.hasText(device.getName()) ? device.getName() : device.getDeviceId()); gbChannel.setDeviceCode(device.getDeviceId()); gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE"); gbChannel.setEnableMp4("0"); @@ -860,7 +876,7 @@ GbChannel gbChannel = new GbChannel(); gbChannel.setGbDeviceId(device.getDeviceId()); gbChannel.setGbChannelId(channelId); gbChannel.setChannelName(channel.getName() != null ? channel.getName() : channelId); gbChannel.setChannelName(StringUtils.hasText(channel.getName()) ? channel.getName() : channelId); gbChannel.setDeviceCode(channelId); gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE"); gbChannel.setEnableMp4("0"); @@ -876,4 +892,98 @@ return R.ok("迁移完成: 设备 " + deviceCount + " 条, 通道 " + channelCount + " 条"); } /** * 从 Redis 同步通道数据到 MySQL,使 ard_gb_channel 与 Redis 一致 */ @Operation(summary = "从Redis同步通道到MySQL") @PostMapping("/syncGbChannelsFromRedis") public R<String> syncGbChannelsFromRedis() { List<Device> allDevices = deviceService.getAllRedisDevices(); int createdCount = 0; int deletedCount = 0; int skippedCount = 0; for (Device device : allDevices) { try { String deviceId = device.getDeviceId(); List<DeviceChannel> redisChannels = deviceService.getChannelsByDeviceId(deviceId); if (redisChannels == null) { redisChannels = new ArrayList<>(); } Set<String> redisChannelIds = new HashSet<>(); boolean hasRealSubChannel = false; for (DeviceChannel ch : redisChannels) { String channelId = ch.getDeviceId(); if (channelId == null || channelId.length() <= 8) { continue; } if (channelId.length() == 20) { try { GbCode gbCode = GbCode.decode(channelId); if (gbCode != null && ("215".equals(gbCode.getTypeCode()) || "216".equals(gbCode.getTypeCode()))) { continue; } } catch (Exception ignored) {} } redisChannelIds.add(channelId); if (!channelId.equals(deviceId)) { hasRealSubChannel = true; } GbChannel existing = gbChannelService.selectByGbDeviceIdAndGbChannelId(deviceId, channelId); if (existing == null) { GbChannel gbChannel = new GbChannel(); gbChannel.setGbDeviceId(deviceId); gbChannel.setGbChannelId(channelId); gbChannel.setChannelName(StringUtils.hasText(ch.getName()) ? ch.getName() : channelId); gbChannel.setDeviceCode(channelId); gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE"); gbChannel.setEnableMp4("0"); gbChannelService.insertGbChannel(gbChannel); createdCount++; log.info("[同步] 创建通道: deviceId={}, channelId={}", deviceId, channelId); } else if (StringUtils.hasText(ch.getName()) && !ch.getName().equals(existing.getChannelName())) { existing.setChannelName(ch.getName()); gbChannelService.updateGbChannel(existing); log.info("[同步] 更新通道名称: deviceId={}, channelId={}, {} -> {}", deviceId, channelId, existing.getChannelName(), ch.getName()); } } List<GbChannel> mysqlChannels = gbChannelService.selectGbChannelByGbDeviceId(deviceId); for (GbChannel mysqlCh : mysqlChannels) { if (!redisChannelIds.contains(mysqlCh.getGbChannelId())) { if ("1".equals(mysqlCh.getStreamStatus())) { skippedCount++; continue; } gbChannelService.deleteGbChannelById(mysqlCh.getId()); deletedCount++; } } if (hasRealSubChannel) { GbChannel defaultChannel = gbChannelService.selectByGbDeviceIdAndGbChannelId(deviceId, deviceId); if (defaultChannel != null && !redisChannelIds.contains(deviceId)) { if (!"1".equals(defaultChannel.getStreamStatus())) { gbChannelService.deleteGbChannelById(defaultChannel.getId()); deletedCount++; } else { skippedCount++; } } } } catch (Exception e) { log.error("[同步] 处理设备失败: {}", device.getDeviceId(), e); } } return R.ok(String.format("同步完成: 创建 %d 条, 删除 %d 条, 跳过 %d 条(推流中)", createdCount, deletedCount, skippedCount)); } } ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/common/VideoManagerConstants.java
@@ -18,8 +18,8 @@ public static final String ONLINE_MEDIA_SERVERS_PREFIX = "VMP_ONLINE_MEDIA_SERVERS:"; public static final String DEVICE_PREFIX = "QS_DEVICE_INFO"; public static final String DEVICE_CHANNEL_PREFIX = "QS_DEVICE_CHANNEL_INFO"; public static final String DEVICE_PREFIX = "GB_DEVICE_INFO"; public static final String DEVICE_CHANNEL_PREFIX = "GB_DEVICE_CHANNEL_INFO"; public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO"; @@ -34,7 +34,7 @@ public static final String MEDIA_STREAM_AUTHORITY = "VMP_MEDIA_STREAM_AUTHORITY"; public static final String SIP_CSEQ_PREFIX = "QS_SIP_CSEQ_"; public static final String SIP_CSEQ_PREFIX = "GB_SIP_CSEQ_"; public static final String SIP_SUBSCRIBE_PREFIX = "VMP_SIP_SUBSCRIBE_"; ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/IDeviceChannelService.java
@@ -16,7 +16,7 @@ * * @param deviceId */ void cleanChannelsForDevice(int deviceId); void cleanChannelsForDevice(String deviceId); /** * 批量添加设备通道 ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/IDeviceService.java
@@ -68,13 +68,20 @@ DeviceChannel getDeviceChannelByChannelId(String gbDeviceId, String gbChannelId); /** * 获取所有国标设备 * 获取所有国标设备(从数据库查询,Redis 补充运行时信息) * * @return 设备列表 */ List<Device> getAllDevices(); /** * 获取所有国标设备(直接从 Redis 查询,用于迁移/同步等场景) * * @return 设备列表 */ List<Device> getAllRedisDevices(); /** * 根据设备id获取所有通道 * * @param gbDeviceId 设备编号 ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/IRedisCatchStorage.java
@@ -34,7 +34,7 @@ * * @param deviceId */ void cleanChannelsForDevice(int deviceId); void cleanChannelsForDevice(String deviceId); /** * 查询全部通道刷新 ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceChannelServiceImpl.java
@@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.*; import java.util.stream.Collectors; @@ -34,7 +35,7 @@ * @param deviceId */ @Override public void cleanChannelsForDevice(int deviceId) { public void cleanChannelsForDevice(String deviceId) { redisCatchStorage.cleanChannelsForDevice(deviceId); } @@ -62,8 +63,9 @@ * 自动为设备下的每个通道创建 GbChannel 记录 */ private void autoCreateGbChannelForChannels(Device device, List<DeviceChannel> channels) { String parentDeviceId = device.getDeviceId(); try { String parentDeviceId = device.getDeviceId(); boolean hasRealSubChannel = false; for (DeviceChannel channel : channels) { String channelId = channel.getDeviceId(); if (channelId == null) { @@ -84,23 +86,44 @@ } } // 判断是否有真实子通道(不是设备自身的默认通道) if (!channelId.equals(parentDeviceId)) { hasRealSubChannel = true; } // 检查是否已存在 GbChannel existing = gbChannelService.selectByGbDeviceIdAndGbChannelId(parentDeviceId, channelId); if (existing == null) { GbChannel gbChannel = new GbChannel(); gbChannel.setGbDeviceId(parentDeviceId); gbChannel.setGbChannelId(channelId); gbChannel.setChannelName(channel.getName() != null ? channel.getName() : channelId); gbChannel.setChannelName(StringUtils.hasText(channel.getName()) ? channel.getName() : channelId); gbChannel.setDeviceCode(channelId); gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE"); gbChannel.setEnableMp4("0"); gbChannelService.insertGbChannel(gbChannel); log.info("[GbChannel自动创建] parentDeviceId={}, channelId={}, id={}", parentDeviceId, channelId, gbChannel.getId()); } else if (StringUtils.hasText(channel.getName()) && !channel.getName().equals(existing.getChannelName())) { // 已存在但名称有变化,从目录同步更新名称 existing.setChannelName(channel.getName()); gbChannelService.updateGbChannel(existing); log.info("[GbChannel更新名称] parentDeviceId={}, channelId={}, {} -> {}", parentDeviceId, channelId, existing.getChannelName(), channel.getName()); } } // 如果有真实子通道,删除注册时创建的默认通道(gb_channel_id = gb_device_id) if (hasRealSubChannel) { GbChannel defaultChannel = gbChannelService.selectByGbDeviceIdAndGbChannelId(parentDeviceId, parentDeviceId); if (defaultChannel != null) { gbChannelService.deleteGbChannelById(defaultChannel.getId()); log.info("[GbChannel清理默认通道] parentDeviceId={}, 有真实子通道,删除默认通道 id={}", parentDeviceId, defaultChannel.getId()); } } } catch (Exception e) { log.error("[GbChannel自动创建] 失败 parentDeviceId={}", device.getDeviceId(), e); log.error("[GbChannel自动创建] 失败 parentDeviceId={}", parentDeviceId, e); } } ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceServiceImpl.java
@@ -26,13 +26,13 @@ import com.ard.gb28181.task.deviceSubscribe.deviceSubscribe.SubscribeTaskRunner; import com.ard.gb28181.task.deviceSubscribe.deviceSubscribe.impl.SubscribeTaskForCatalog; import com.ard.gb28181.task.deviceSubscribe.deviceSubscribe.impl.SubscribeTaskForMobilPosition; import com.ard.qs.api.RemoteQsDeviceService; import com.ard.zlm.api.RemoteZlmService; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.sip.InvalidArgumentException; import javax.sip.SipException; @@ -72,9 +72,6 @@ @Autowired private RemoteZlmService remoteZlmService; @Autowired private RemoteQsDeviceService remoteQsDeviceService; @Autowired private IGbDeviceService gbDeviceService; @@ -180,23 +177,20 @@ DeviceStatusTask task = DeviceStatusTask.getInstance(device.getDeviceId(), sipTransactionInfo, expiresTime + System.currentTimeMillis(), this::deviceStatusExpire); deviceStatusTaskRunner.addTask(task); } // 同步设备状态到 QS 模块 try { remoteQsDeviceService.updateDeviceStatusByGbDeviceId(device.getDeviceId(), "ON", SecurityConstants.INNER); } catch (Exception e) { log.error("[同步设备状态] 设备上线,同步到 QS 模块失败:{}", device.getDeviceId(), e); } // 设备注册成功时,自动在 ard_gb_device 表中创建设备记录(若不存在) try { GbDevice existingDevice = gbDeviceService.selectGbDeviceByGbDeviceId(device.getDeviceId()); if (existingDevice == null) { GbDevice gbDevice = new GbDevice(); gbDevice.setGbDeviceId(device.getDeviceId()); gbDevice.setDeviceName(device.getName() != null ? device.getName() : device.getDeviceId()); gbDevice.setDeviceName(StringUtils.hasText(device.getName()) ? device.getName() : device.getDeviceId()); gbDevice.setDeviceCode(device.getDeviceId()); gbDevice.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE"); gbDevice.setEnableMp4("0"); gbDevice.setIp(device.getIp()); gbDevice.setPort(device.getPort()); gbDevice.setManufacturer(device.getManufacturer()); gbDevice.setModel(device.getModel()); gbDevice.setFirmware(device.getFirmware()); gbDevice.setOnLine(true); gbDeviceService.insertGbDevice(gbDevice); log.info("[GbDevice自动创建] deviceId: {}, id: {}", device.getDeviceId(), gbDevice.getId()); @@ -204,13 +198,52 @@ GbChannel gbChannel = new GbChannel(); gbChannel.setGbDeviceId(device.getDeviceId()); gbChannel.setGbChannelId(device.getDeviceId()); gbChannel.setChannelName(device.getName() != null ? device.getName() : device.getDeviceId()); gbChannel.setChannelName(StringUtils.hasText(device.getName()) ? device.getName() : device.getDeviceId()); gbChannel.setDeviceCode(device.getDeviceId()); gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE"); gbChannel.setEnableMp4("0"); gbChannelService.insertGbChannel(gbChannel); log.info("[GbChannel默认创建] deviceId: {}, channelId: {}, id: {}", device.getDeviceId(), device.getDeviceId(), gbChannel.getId()); } else { // 设备已存在,更新最新信息(名称、IP、端口、厂商、型号、固件) boolean needUpdate = false; String newName = StringUtils.hasText(device.getName()) ? device.getName() : device.getDeviceId(); if (!newName.equals(existingDevice.getDeviceName())) { existingDevice.setDeviceName(newName); needUpdate = true; } if (device.getIp() != null && !device.getIp().equals(existingDevice.getIp())) { existingDevice.setIp(device.getIp()); needUpdate = true; } if (device.getPort() > 0 && (existingDevice.getPort() == null || device.getPort() != existingDevice.getPort())) { existingDevice.setPort(device.getPort()); needUpdate = true; } if (device.getManufacturer() != null && !device.getManufacturer().equals(existingDevice.getManufacturer())) { existingDevice.setManufacturer(device.getManufacturer()); needUpdate = true; } if (device.getModel() != null && !device.getModel().equals(existingDevice.getModel())) { existingDevice.setModel(device.getModel()); needUpdate = true; } if (device.getFirmware() != null && !device.getFirmware().equals(existingDevice.getFirmware())) { existingDevice.setFirmware(device.getFirmware()); needUpdate = true; } if (existingDevice.getOnLine() == null || !existingDevice.getOnLine()) { existingDevice.setOnLine(true); needUpdate = true; } if (needUpdate) { gbDeviceService.updateGbDevice(existingDevice); log.info("[GbDevice更新] deviceId: {}, name={}, ip={}, port={}, manufacturer={}, model={}, firmware={}", device.getDeviceId(), existingDevice.getDeviceName(), existingDevice.getIp(), existingDevice.getPort(), existingDevice.getManufacturer(), existingDevice.getModel(), existingDevice.getFirmware()); } } } catch (Exception e) { log.error("[GbDevice自动创建] 设备上线,自动创建失败:{}", device.getDeviceId(), e); @@ -242,11 +275,46 @@ if (isDevice(deviceId)) { channelOfflineByDevice(device); } // 同步设备状态到 QS 模块 // 同步更新 MySQL 在线状态 try { remoteQsDeviceService.updateDeviceStatusByGbDeviceId(deviceId, "OFFLINE", SecurityConstants.INNER); GbDevice gbDevice = gbDeviceService.selectGbDeviceByGbDeviceId(deviceId); if (gbDevice != null) { gbDevice.setOnLine(false); gbDeviceService.updateGbDevice(gbDevice); log.info("[设备离线] 更新MySQL在线状态: {} -> offline", deviceId); } } catch (Exception e) { log.error("[同步设备状态] 设备离线,同步到 QS 模块失败:{}", deviceId, e); log.error("[设备离线] 更新MySQL在线状态失败: {}", deviceId, e); } // 主动注销时:清理 Redis 中的通道数据,删除 MySQL 中该设备的通道记录 if ("主动注销".equals(reason)) { try { redisCatchStorage.cleanChannelsForDevice(deviceId); log.info("[设备离线] 主动注销,已清理Redis通道: {}", deviceId); } catch (Exception e) { log.error("[设备离线] 清理Redis通道失败: {}", deviceId, e); } try { List<GbChannel> channels = gbChannelService.selectGbChannelByGbDeviceId(deviceId); int deletedCount = 0; int skippedCount = 0; for (GbChannel ch : channels) { // 跳过正在推流的通道,避免误删 if ("1".equals(ch.getStreamStatus())) { ch.setStreamStatus("0"); gbChannelService.updateGbChannelStream(ch); skippedCount++; continue; } gbChannelService.deleteGbChannelById(ch.getId()); deletedCount++; } log.info("[设备离线] 主动注销,已清理MySQL通道: {}条删除, {}条跳过", deletedCount, skippedCount); } catch (Exception e) { log.error("[设备离线] 清理MySQL通道失败: {}", deviceId, e); } } } @@ -422,12 +490,59 @@ } /** * 获取所有国标设备 * 获取所有国标设备(从数据库查询,Redis 补充在线状态等运行时信息) * * @return 设备列表 */ @Override public List<Device> getAllDevices() { List<Device> result = new java.util.ArrayList<>(); List<GbDevice> gbDevices = gbDeviceService.selectGbDeviceList(new GbDevice()); for (GbDevice gbDevice : gbDevices) { // 从 Redis 获取运行时信息(在线状态、IP、端口等) Device redisDevice = redisCatchStorage.getDevice(gbDevice.getGbDeviceId()); if (redisDevice != null) { // 用数据库的名称/厂商/型号覆盖 Redis 数据 if (StringUtils.hasText(gbDevice.getDeviceName())) { redisDevice.setName(gbDevice.getDeviceName()); } if (StringUtils.hasText(gbDevice.getManufacturer())) { redisDevice.setManufacturer(gbDevice.getManufacturer()); } if (StringUtils.hasText(gbDevice.getModel())) { redisDevice.setModel(gbDevice.getModel()); } if (StringUtils.hasText(gbDevice.getFirmware())) { redisDevice.setFirmware(gbDevice.getFirmware()); } result.add(redisDevice); } else { // Redis 中没有(设备离线已久),用数据库数据构造基本 Device 对象 Device device = new Device(); device.setDeviceId(gbDevice.getGbDeviceId()); device.setName(StringUtils.hasText(gbDevice.getDeviceName()) ? gbDevice.getDeviceName() : gbDevice.getGbDeviceId()); device.setOnLine(gbDevice.getOnLine() != null && gbDevice.getOnLine()); device.setIp(gbDevice.getIp()); device.setPort(gbDevice.getPort() != null ? gbDevice.getPort() : 0); device.setManufacturer(gbDevice.getManufacturer()); device.setPort(gbDevice.getPort() != null ? gbDevice.getPort() : 0); device.setManufacturer(gbDevice.getManufacturer()); device.setModel(gbDevice.getModel()); device.setFirmware(gbDevice.getFirmware()); result.add(device); } } return result; } /** * 获取所有国标设备(直接从 Redis 查询,用于迁移/同步等场景) * * @return 设备列表 */ @Override public List<Device> getAllRedisDevices() { return redisCatchStorage.getAllDevices(); } ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/RedisCatchStorageImpl.java
@@ -75,7 +75,7 @@ * @param deviceId */ @Override public void cleanChannelsForDevice(int deviceId) { public void cleanChannelsForDevice(String deviceId) { String key = VideoManagerConstants.DEVICE_CHANNEL_PREFIX; redisTemplate.opsForHash().delete(key, deviceId); } ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/task/deviceStatus/DeviceStatusTaskRunner.java
@@ -31,7 +31,7 @@ @Autowired private UserSetting userSetting; private final String prefix = "QS_DEVICE_STATUS"; private final String prefix = "GB_DEVICE_STATUS"; @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) public void expirationCheck() { ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
@@ -115,6 +115,13 @@ device.setRegisterTime(DateUtil.getNow()); deviceService.online(device, null); } else { // 注销续订:回复200 OK后执行离线 try { Response unregisterOkResponse = getRegisterOkResponse(request); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), unregisterOkResponse); } catch (SipException | ParseException e) { log.error("[注销续订] 回复200 OK失败: {}", e.getMessage()); } deviceService.offline(deviceId, "主动注销"); } return; @@ -185,6 +192,11 @@ device.setGeoCoordSys("WGS84"); } } // 从SIP From头中提取设备名称 String displayName = address.getDisplayName(); if (StringUtils.isNotEmpty(displayName)) { device.setName(displayName); } //国标协议版本(2022) Header header = request.getHeader("X-GB-Ver"); ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
@@ -1,6 +1,5 @@ package com.ard.gb28181.transmit.event.request.impl.message.response.cmd; import com.ard.common.core.utils.DateUtils; import com.ard.gb28181.config.SipConfig; import com.ard.gb28181.api.domain.Device; import com.ard.gb28181.api.domain.DeviceChannel; @@ -11,10 +10,6 @@ import com.ard.gb28181.transmit.event.request.impl.message.IMessageHandler; import com.ard.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; import com.ard.gb28181.utils.Coordtransform; import com.ard.qs.api.common.CivilCodePo; import com.ard.qs.api.domain.QsGroup; import com.ard.qs.api.domain.QsRegion; import com.ard.qs.api.utils.CivilCodeUtil; import gov.nist.javax.sip.message.SIPRequest; import lombok.extern.slf4j.Slf4j; import org.dom4j.DocumentException; @@ -117,14 +112,12 @@ if (sumNum == 0) { log.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId()); // 数据已经完整接收 deviceChannelService.cleanChannelsForDevice(take.getDevice().getId()); deviceChannelService.cleanChannelsForDevice(take.getDevice().getDeviceId()); return; } else { Iterator<Element> deviceListIterator = deviceListElement.elementIterator(); if (deviceListIterator != null) { List<DeviceChannel> channelList = new ArrayList<>(); List<QsRegion> regionList = new ArrayList<>(); List<QsGroup> groupList = new ArrayList<>(); // 遍历DeviceList while (deviceListIterator.hasNext()) { Element itemDevice = deviceListIterator.next(); @@ -147,42 +140,15 @@ // 解析通道类型 if (channel.getDeviceId().length() <= 8) { // 行政区划 QsRegion region = new QsRegion(); region.setName(channel.getName()); region.setDeviceId(channel.getDeviceId()); CivilCodePo parentCode = CivilCodeUtil.INSTANCE.getParentCode(channel.getDeviceId()); if (parentCode != null) { region.setParentDeviceId(parentCode.getCode()); } region.setCreateTime(DateUtils.getNowDate()); region.setUpdateTime(DateUtils.getNowDate()); regionList.add(region); channel.setChannelType(1); } else if (channel.getDeviceId().length() == 20) { // 业务分组/虚拟组织 GbCode gbCode = GbCode.decode(channel.getDeviceId()); if (gbCode == null || (!gbCode.getTypeCode().equals("215") && !gbCode.getTypeCode().equals("216"))) { // channelList.add(null); } QsGroup group = new QsGroup(); group.setName(channel.getName()); group.setDeviceId(channel.getDeviceId()); group.setCreateTime(DateUtils.getNowDate()); group.setUpdateTime(DateUtils.getNowDate()); if (gbCode.getTypeCode().equals("215")) { group.setBusinessGroup(channel.getDeviceId()); } else if (gbCode.getTypeCode().equals("216")) { group.setBusinessGroup(channel.getBusinessGroupId()); group.setParentDeviceId(channel.getParentId()); } if (group.getBusinessGroup() == null) { // channelList.add(null); } if (group != null) { if (gbCode != null && ("215".equals(gbCode.getTypeCode()) || "216".equals(gbCode.getTypeCode()))) { // 业务分组/虚拟组织 channel.setParental(1); channel.setChannelType(2); groupList.add(group); } // 坐标转换(所有20位编码通道,含普通摄像头和业务分组) if (channel.getLongitude() != null && channel.getLatitude() != null && channel.getLongitude() > 0 && channel.getLatitude() > 0) { Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(channel.getLongitude(), channel.getLatitude()); channel.setGbLongitude(wgs84Position[0]); ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java
@@ -1,7 +1,9 @@ package com.ard.gb28181.transmit.event.request.impl.message.response.cmd; import com.ard.gb28181.api.domain.Device; import com.ard.gb28181.domain.GbDevice; import com.ard.gb28181.service.IDeviceService; import com.ard.gb28181.service.IGbDeviceService; import com.ard.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.ard.gb28181.transmit.event.request.impl.message.IMessageHandler; import com.ard.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; @@ -36,6 +38,9 @@ @Autowired private IDeviceService deviceService; @Autowired private IGbDeviceService gbDeviceService; @Override public void afterPropertiesSet() throws Exception { @@ -72,6 +77,45 @@ device.setStreamMode("TCP-PASSIVE"); } deviceService.updateDevice(device); // 同步更新 ard_gb_device 表中的设备名称、IP、端口、厂商、型号、固件 try { GbDevice gbDevice = gbDeviceService.selectGbDeviceByGbDeviceId(device.getDeviceId()); if (gbDevice != null) { boolean needUpdate = false; if (device.getName() != null && !device.getName().equals(gbDevice.getDeviceName())) { gbDevice.setDeviceName(device.getName()); needUpdate = true; } if (device.getIp() != null && !device.getIp().equals(gbDevice.getIp())) { gbDevice.setIp(device.getIp()); needUpdate = true; } if (gbDevice.getPort() == null || device.getPort() != gbDevice.getPort()) { gbDevice.setPort(device.getPort()); needUpdate = true; } if (device.getManufacturer() != null && !device.getManufacturer().equals(gbDevice.getManufacturer())) { gbDevice.setManufacturer(device.getManufacturer()); needUpdate = true; } if (device.getModel() != null && !device.getModel().equals(gbDevice.getModel())) { gbDevice.setModel(device.getModel()); needUpdate = true; } if (device.getFirmware() != null && !device.getFirmware().equals(gbDevice.getFirmware())) { gbDevice.setFirmware(device.getFirmware()); needUpdate = true; } if (needUpdate) { gbDeviceService.updateGbDevice(gbDevice); log.info("[DeviceInfo] 更新设备信息: {} -> name={}, ip={}, port={}, manufacturer={}, model={}, firmware={}", device.getDeviceId(), device.getName(), device.getIp(), device.getPort(), device.getManufacturer(), device.getModel(), device.getFirmware()); } } } catch (Exception e) { log.error("[DeviceInfo] 更新GbDevice信息失败: {}", device.getDeviceId(), e); } responseMessageHandler.handMessageEvent(rootElement, device); } catch (DocumentException e) { ard-modules/ard-modules-gb28181/src/main/resources/banner.txt
@@ -1,7 +1,7 @@ Spring Boot Version: ${spring-boot.version} Spring Application Name: ${spring.application.name} _ ____ ____ ____ ____ ____ ___ _ ___ _ ___ / \ | _ \ | _ \ / ___| | __ ) |___ \ ( _ ) / | ( _ ) / | ( _ ) / _ \ | |_) | | | | | _____ | | _ | _ \ __) | / _ \ | | / _ \ | | / _ \ / ___ \ | _ < | |_| | |_____| | |_| | | |_) | / __/ | (_) | | | | (_) | | | | (_) | /_/ \_\ |_| \_\ |____/ \____| |____/ |_____| \___/ |_| \___/ |_| \___/ _ ____ ____ ____ ____ ____ ___ _ ___ _ / \ | _ \ | _ \ / ___| | __ ) |___ \ ( _ ) / | ( _ ) / | / _ \ | |_) | | | | | _____ | | _ | _ \ __) | / _ \ | | / _ \ | | / ___ \ | _ < | |_| | |_____| | |_| | | |_) | / __/ | (_) | | | | (_) | | | /_/ \_\ |_| \_\ |____/ \____| |____/ |_____| \___/ |_| \___/ |_| ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/controller/ZlmController.java
@@ -780,4 +780,145 @@ return AjaxResult.success(json); } /** * gb28181 播放(基于GbChannel主键ID) * * @param request * @param channelId GbChannel主键ID * @return */ @Operation(summary = "gb28181播放(按通道主键ID)") @GetMapping("/startGb28181PlayByChannelId/{channelId}") public DeferredResult<R<StreamContent>> startGb28181PlayByChannelId( HttpServletRequest request, @PathVariable Long channelId ) { log.info("[gb28181 开始点播] channelId:{}", channelId); Assert.notNull(channelId, "通道ID不可为空"); // 1. 根据主键ID查询 GbChannel R<GbChannelDTO> channelR = remoteGb28181Service.getGbChannelById(channelId, SecurityConstants.INNER); if (channelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("获取GbChannel失败 channelId:" + channelId); } Assert.notNull(channelR.getData(), "GbChannel不存在 channelId:" + channelId); GbChannelDTO gbChannelDTO = channelR.getData(); String gbDeviceId = gbChannelDTO.getGbDeviceId(); String gbChannelId = gbChannelDTO.getGbChannelId(); // 2. 查询国标设备 R<Device> deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId); if (!deviceR.getData().isOnLine()) { throw new RuntimeException("gb28181 国标设备不在线 deviceId:" + gbDeviceId); } // 3. 查询国标通道 R<DeviceChannel> deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); if (!"ON".equals(deviceChannelR.getData().getStatus())) { throw new RuntimeException("gb28181 国标设备通道不在线 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } DeferredResult<R<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(() -> { log.info("[点播等待超时] channelId:{}, gbDeviceId:{}, gbChannelId:{}", channelId, gbDeviceId, gbChannelId); R<StreamContent> fail = R.fail(); fail.setMsg("点播超时"); result.setResult(fail); inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, gbChannelDTO.getId()); mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, gbChannelDTO, deviceR.getData(), gbChannelDTO.getDeviceCode()); }); ErrorCallback<StreamInfo> callback = (code, msg, streamInfo) -> { if (code == InviteErrorCode.SUCCESS.getCode()) { R<StreamContent> r = R.ok(); if (streamInfo != null) { if (userSetting.getUseSourceIpAsStreamIp()) { streamInfo = streamInfo.clone(); String host; try { URL url = new URL(request.getRequestURL().toString()); host = url.getHost(); } catch (MalformedURLException e) { host = request.getLocalAddr(); } streamInfo.changeStreamIp(host); } if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) { streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix()); } r.setData(new StreamContent(streamInfo)); } else { r.setCode(code); r.setMsg(msg); } result.setResult(r); } else { result.setResult(R.fail(code, msg)); } }; mediaServerService.startGb28181PlayByGbChannel(gbChannelDTO, deviceR.getData(), callback); return result; } /** * gb28181 停止点播(基于GbChannel主键ID) * * @param channelId GbChannel主键ID * @return */ @Operation(summary = "gb28181停止点播(按通道主键ID)") @GetMapping("/stopGb28181PlayByChannelId/{channelId}") public AjaxResult stopGb28181PlayByChannelId(@PathVariable Long channelId) { log.info("[gb28181 停止点播] channelId:{}", channelId); Assert.notNull(channelId, "通道ID不可为空"); // 1. 根据主键ID查询 GbChannel R<GbChannelDTO> channelR = remoteGb28181Service.getGbChannelById(channelId, SecurityConstants.INNER); if (channelR.getCode() != Constants.SUCCESS || channelR.getData() == null) { throw new RuntimeException("GbChannel不存在 channelId:" + channelId); } GbChannelDTO gbChannelDTO = channelR.getData(); String gbDeviceId = gbChannelDTO.getGbDeviceId(); String gbChannelId = gbChannelDTO.getGbChannelId(); // 2. 查询国标设备 R<Device> deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER); if (deviceR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId); } Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId); // 3. 查询国标通道 R<DeviceChannel> deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER); if (deviceChannelR.getCode() != Constants.SUCCESS) { throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); } Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId); mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, gbChannelDTO, deviceR.getData(), gbChannelDTO.getDeviceCode()); JSONObject json = new JSONObject(); json.put("channelId", channelId); json.put("gbDeviceId", gbDeviceId); json.put("gbChannelId", gbChannelId); return AjaxResult.success(json); } }