From 7f895a7b0f9510d4fe3898b405d4827322a8edcb Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Thu, 19 Mar 2026 11:34:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9A=E6=91=84=E5=83=8F?= =?UTF-8?q?=E5=A4=B4=E5=91=BD=E5=90=8D=E6=94=B9=E9=80=A0=20=E2=80=94=20cam?= =?UTF-8?q?era=5Fname=20+=20=E6=96=B0=E7=BC=96=E7=A0=81=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 camera_name 字段(用户自定义名称,必填) - camera_code 生成规则改为 CAM{YYYYMMDD}{NNN}(日期+序号) - INSERT/UPDATE SQL 支持 camera_name - 新增 /api/ai/camera/options 接口(前端下拉选择用) - 存量数据迁移脚本:camera_name = app + stream --- .../aiot/controller/AiCameraController.java | 18 +++++++ .../iot/vmp/aiot/util/CameraCodeUtil.java | 54 +++++++++++-------- .../iot/vmp/streamProxy/bean/StreamProxy.java | 5 +- .../streamProxy/dao/StreamProxyMapper.java | 17 +++++- .../service/impl/StreamProxyServiceImpl.java | 38 +++++++------ 数据库/aiot/迁移-添加camera_name字段.sql | 16 ++++++ 6 files changed, 103 insertions(+), 45 deletions(-) create mode 100644 数据库/aiot/迁移-添加camera_name字段.sql diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiCameraController.java b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiCameraController.java index 9eff426fe..c6896bc9b 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiCameraController.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiCameraController.java @@ -10,6 +10,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + /** * AI摄像头管理接口 */ @@ -38,4 +43,17 @@ public class AiCameraController { return WVPResult.success(proxy); } + + @Operation(summary = "获取摄像头选项列表(用于下拉选择)") + @GetMapping("/options") + public WVPResult>> getCameraOptions() { + List list = streamProxyMapper.selectAllCameraOptions(); + List> options = list.stream().map(p -> { + Map m = new HashMap<>(); + m.put("cameraCode", p.getCameraCode()); + m.put("cameraName", p.getCameraName() != null ? p.getCameraName() : p.getApp()); + return m; + }).collect(Collectors.toList()); + return WVPResult.success(options); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/util/CameraCodeUtil.java b/src/main/java/com/genersoft/iot/vmp/aiot/util/CameraCodeUtil.java index 2cc8f127a..ce0e50700 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/util/CameraCodeUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/util/CameraCodeUtil.java @@ -1,37 +1,47 @@ package com.genersoft.iot.vmp.aiot.util; -import java.util.UUID; +import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; /** * 摄像头编码生成工具类 - * 从 RTSP URL 中提取 IP/Host 特征,生成可读的 camera_code - * 格式:cam_{host下划线分隔}_{4位随机} - * 例:rtsp://admin:pwd@192.168.1.100:554/stream → cam_192_168_1_100_a3f1 + * 格式:CAM{YYYYMMDD}{NNN} 例:CAM20260319001 + * 日期部分标识创建日期,序号部分为当日递增 */ public class CameraCodeUtil { + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd"); + private CameraCodeUtil() {} - public static String generate(String srcUrl) { - String hostPart = "unknown"; - if (srcUrl != null && !srcUrl.isEmpty()) { + /** + * 生成新的 camera_code(需要查询数据库获取当日最大序号) + */ + public static String generate(StreamProxyMapper mapper) { + String dateStr = LocalDate.now().format(DATE_FMT); + String prefix = "CAM" + dateStr; + + // 查询当日最大编码 + String maxCode = mapper.selectMaxCameraCodeByPrefix(prefix); + int nextSeq = 1; + if (maxCode != null && maxCode.length() > prefix.length()) { try { - String urlBody = srcUrl; - int schemeEnd = urlBody.indexOf("://"); - if (schemeEnd >= 0) { - urlBody = urlBody.substring(schemeEnd + 3); - } - int atIndex = urlBody.indexOf('@'); - if (atIndex >= 0) { - urlBody = urlBody.substring(atIndex + 1); - } - String host = urlBody.split("[:/]")[0]; - hostPart = host.replace(".", "_"); - } catch (Exception e) { - hostPart = "unknown"; + nextSeq = Integer.parseInt(maxCode.substring(prefix.length())) + 1; + } catch (NumberFormatException e) { + nextSeq = 1; } } - String suffix = UUID.randomUUID().toString().replace("-", "").substring(0, 4); - return "cam_" + hostPart + "_" + suffix; + return prefix + String.format("%03d", nextSeq); + } + + /** + * 兼容旧接口:从 RTSP URL 生成(降级方案) + */ + public static String generateFromUrl(String srcUrl) { + String dateStr = LocalDate.now().format(DATE_FMT); + String hash = String.format("%03d", Math.abs(srcUrl.hashCode()) % 999 + 1); + return "CAM" + dateStr + hash; } } diff --git a/src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxy.java b/src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxy.java index d269dcbfe..fd5277a67 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxy.java +++ b/src/main/java/com/genersoft/iot/vmp/streamProxy/bean/StreamProxy.java @@ -30,9 +30,12 @@ public class StreamProxy extends CommonGBChannel { @Schema(description = "流ID") private String stream; - @Schema(description = "摄像头全局唯一编码(格式:cam_xxxxxxxxxxxx)") + @Schema(description = "摄像头全局唯一编码(格式:CAM{YYYYMMDD}{NNN})") private String cameraCode; + @Schema(description = "摄像头名称(用户自定义)") + private String cameraName; + @Schema(description = "当前拉流使用的流媒体服务ID") private String mediaServerId; diff --git a/src/main/java/com/genersoft/iot/vmp/streamProxy/dao/StreamProxyMapper.java b/src/main/java/com/genersoft/iot/vmp/streamProxy/dao/StreamProxyMapper.java index 12b7a03f6..da7787cbd 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamProxy/dao/StreamProxyMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/streamProxy/dao/StreamProxyMapper.java @@ -13,10 +13,10 @@ public interface StreamProxyMapper { @Insert("INSERT INTO wvp_stream_proxy (type, app, stream,relates_media_server_id, src_url, " + "timeout, ffmpeg_cmd_key, rtsp_type, enable_audio, enable_mp4, enable, pulling, " + - "enable_disable_none_reader, server_id, create_time, camera_code, area_id) VALUES" + + "enable_disable_none_reader, server_id, create_time, camera_code, camera_name, area_id) VALUES" + "(#{type}, #{app}, #{stream}, #{relatesMediaServerId}, #{srcUrl}, " + "#{timeout}, #{ffmpegCmdKey}, #{rtspType}, #{enableAudio}, #{enableMp4}, #{enable}, #{pulling}, " + - "#{enableDisableNoneReader}, #{serverId}, #{createTime}, #{cameraCode}, #{areaId} )") + "#{enableDisableNoneReader}, #{serverId}, #{createTime}, #{cameraCode}, #{cameraName}, #{areaId} )") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int add(StreamProxy streamProxyDto); @@ -34,6 +34,7 @@ public interface StreamProxyMapper { "pulling=#{pulling}, " + "enable_disable_none_reader=#{enableDisableNoneReader}, " + "enable_mp4=#{enableMp4}, " + + "camera_name=#{cameraName}, " + "area_id=#{areaId} " + "WHERE id=#{id}") int update(StreamProxy streamProxyDto); @@ -113,4 +114,16 @@ public interface StreamProxyMapper { */ @Update("UPDATE wvp_stream_proxy SET camera_code = #{cameraCode} WHERE id = #{id}") int updateCameraCode(@Param("id") int id, @Param("cameraCode") String cameraCode); + + /** + * 查询指定前缀的最大 camera_code(用于生成新编码的序号) + */ + @Select("SELECT MAX(camera_code) FROM wvp_stream_proxy WHERE camera_code LIKE CONCAT(#{prefix}, '%')") + String selectMaxCameraCodeByPrefix(@Param("prefix") String prefix); + + /** + * 查询所有摄像头简要信息(用于前端下拉选择) + */ + @Select("SELECT camera_code, camera_name FROM wvp_stream_proxy WHERE enable = 1 ORDER BY camera_name") + List selectAllCameraOptions(); } diff --git a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java index 416df15cc..a92ceaff9 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java @@ -151,29 +151,27 @@ public class StreamProxyServiceImpl implements IStreamProxyService { throw new ControllerException(ErrorCode.ERROR100.getCode(), "APP+STREAM已经存在"); } - // 自动生成 camera_code(最多重试3次避免冲突) - int retryCount = 0; - while (retryCount < 3) { - String cameraCode = CameraCodeUtil.generate(streamProxy.getSrcUrl()); - streamProxy.setCameraCode(cameraCode); + // 自动生成 camera_code(日期+序号格式) + String cameraCode = CameraCodeUtil.generate(streamProxyMapper); + streamProxy.setCameraCode(cameraCode); - streamProxy.setCreateTime(DateUtil.getNow()); - streamProxy.setUpdateTime(DateUtil.getNow()); + // camera_name 必填校验 + if (streamProxy.getCameraName() == null || streamProxy.getCameraName().trim().isEmpty()) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "摄像头名称不能为空"); + } - try { - if (streamProxy.getGbDeviceId() != null) { - gbChannelService.add(streamProxy.buildCommonGBChannel()); - } - streamProxyMapper.add(streamProxy); - streamProxy.setDataType(ChannelDataType.STREAM_PROXY); - streamProxy.setDataDeviceId(streamProxy.getId()); - return; - } catch (DuplicateKeyException e) { - retryCount++; - if (retryCount >= 3) { - throw new RuntimeException("生成 camera_code 失败,请重试"); - } + streamProxy.setCreateTime(DateUtil.getNow()); + streamProxy.setUpdateTime(DateUtil.getNow()); + + try { + if (streamProxy.getGbDeviceId() != null) { + gbChannelService.add(streamProxy.buildCommonGBChannel()); } + streamProxyMapper.add(streamProxy); + streamProxy.setDataType(ChannelDataType.STREAM_PROXY); + streamProxy.setDataDeviceId(streamProxy.getId()); + } catch (DuplicateKeyException e) { + throw new RuntimeException("生成 camera_code 失败,请重试"); } } diff --git a/数据库/aiot/迁移-添加camera_name字段.sql b/数据库/aiot/迁移-添加camera_name字段.sql new file mode 100644 index 000000000..6335edb40 --- /dev/null +++ b/数据库/aiot/迁移-添加camera_name字段.sql @@ -0,0 +1,16 @@ +-- 摄像头命名改造迁移脚本 +-- 1. 新增 camera_name 字段 +-- 2. 存量数据:camera_name = CONCAT(app, stream) +-- 3. 设置 NOT NULL + +-- Step 1: 添加 camera_name 列 +ALTER TABLE wvp_stream_proxy ADD COLUMN camera_name VARCHAR(100) NULL AFTER camera_code; + +-- Step 2: 存量数据迁移 — app + stream 合并为 camera_name +UPDATE wvp_stream_proxy SET camera_name = CONCAT(IFNULL(app, ''), IFNULL(stream, '')) WHERE camera_name IS NULL; + +-- Step 3: 设置 NOT NULL(确认数据填充后执行) +ALTER TABLE wvp_stream_proxy MODIFY COLUMN camera_name VARCHAR(100) NOT NULL DEFAULT ''; + +-- 验证 +SELECT camera_code, camera_name, app, stream FROM wvp_stream_proxy LIMIT 20;