feat(aiot): 启动时自动回填 camera_code 并修复 ROI 旧格式引用
解决 stream_proxy 表 camera_code 为 NULL 导致配置推送时跳过摄像头的问题: - @PostConstruct 启动时自动为 camera_code 为空的记录生成 cam_xxx 编码 - 自动将 ROI 表中 app/stream 格式的 camera_id 替换为对应的 camera_code - StreamProxyMapper 新增 selectWithNullCameraCode/updateCameraCode 方法 - AiRoiMapper 新增 updateCameraId/queryWithLegacyCameraId 方法 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -61,4 +61,16 @@ public interface AiRoiMapper {
|
|||||||
|
|
||||||
@Update("UPDATE wvp_ai_roi SET device_id=#{deviceId}")
|
@Update("UPDATE wvp_ai_roi SET device_id=#{deviceId}")
|
||||||
int updateAllDeviceId(@Param("deviceId") String deviceId);
|
int updateAllDeviceId(@Param("deviceId") String deviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 ROI 表中 camera_id 从 app/stream 格式更新为 camera_code
|
||||||
|
*/
|
||||||
|
@Update("UPDATE wvp_ai_roi SET camera_id = #{cameraCode} WHERE camera_id = #{oldCameraId}")
|
||||||
|
int updateCameraId(@Param("oldCameraId") String oldCameraId, @Param("cameraCode") String cameraCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询使用非 camera_code 格式的 ROI(即 camera_id 不以 cam_ 开头的记录)
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM wvp_ai_roi WHERE camera_id NOT LIKE 'cam_%'")
|
||||||
|
List<AiRoi> queryWithLegacyCameraId();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.genersoft.iot.vmp.aiot.service.IAiConfigSnapshotService;
|
|||||||
import com.genersoft.iot.vmp.aiot.service.IAiRedisConfigService;
|
import com.genersoft.iot.vmp.aiot.service.IAiRedisConfigService;
|
||||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||||
import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper;
|
import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper;
|
||||||
|
import java.util.UUID;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
@@ -67,10 +68,14 @@ public class AiConfigServiceImpl implements IAiConfigService {
|
|||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动时统一 ROI 表中的 device_id,确保与 Edge 端 EDGE_DEVICE_ID 一致
|
* 启动时自动修复数据一致性:
|
||||||
|
* 1. 统一 ROI 表 device_id
|
||||||
|
* 2. 回填 stream_proxy 缺失的 camera_code
|
||||||
|
* 3. 修复 ROI 表中仍使用 app/stream 格式的 camera_id
|
||||||
*/
|
*/
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void normalizeDeviceIds() {
|
public void normalizeOnStartup() {
|
||||||
|
// 1. 统一 device_id
|
||||||
try {
|
try {
|
||||||
String defaultDeviceId = getDefaultDeviceId();
|
String defaultDeviceId = getDefaultDeviceId();
|
||||||
int updated = roiMapper.updateAllDeviceId(defaultDeviceId);
|
int updated = roiMapper.updateAllDeviceId(defaultDeviceId);
|
||||||
@@ -80,6 +85,74 @@ public class AiConfigServiceImpl implements IAiConfigService {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("[AiConfig] 启动时修复 device_id 失败: {}", e.getMessage());
|
log.warn("[AiConfig] 启动时修复 device_id 失败: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 回填缺失的 camera_code
|
||||||
|
backfillCameraCode();
|
||||||
|
|
||||||
|
// 3. 修复 ROI 表中旧格式的 camera_id(app/stream → camera_code)
|
||||||
|
fixLegacyRoiCameraId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为 stream_proxy 表中 camera_code 为 NULL 的记录自动生成 camera_code
|
||||||
|
*/
|
||||||
|
private void backfillCameraCode() {
|
||||||
|
try {
|
||||||
|
List<StreamProxy> nullCodeProxies = streamProxyMapper.selectWithNullCameraCode();
|
||||||
|
if (nullCodeProxies == null || nullCodeProxies.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int backfilled = 0;
|
||||||
|
for (StreamProxy proxy : nullCodeProxies) {
|
||||||
|
String cameraCode = "cam_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
|
||||||
|
streamProxyMapper.updateCameraCode(proxy.getId(), cameraCode);
|
||||||
|
backfilled++;
|
||||||
|
log.info("[AiConfig] 回填 camera_code: id={}, app={}, stream={} → {}",
|
||||||
|
proxy.getId(), proxy.getApp(), proxy.getStream(), cameraCode);
|
||||||
|
}
|
||||||
|
log.info("[AiConfig] 启动时回填 {} 条 stream_proxy 的 camera_code", backfilled);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[AiConfig] 回填 camera_code 失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复 ROI 表中仍使用 app/stream 格式的 camera_id,替换为对应的 camera_code
|
||||||
|
* 例如: camera_id="live/camera01" → camera_id="cam_a1b2c3d4e5f6"
|
||||||
|
*/
|
||||||
|
private void fixLegacyRoiCameraId() {
|
||||||
|
try {
|
||||||
|
List<AiRoi> legacyRois = roiMapper.queryWithLegacyCameraId();
|
||||||
|
if (legacyRois == null || legacyRois.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int fixed = 0;
|
||||||
|
for (AiRoi roi : legacyRois) {
|
||||||
|
String oldCameraId = roi.getCameraId();
|
||||||
|
if (oldCameraId == null || !oldCameraId.contains("/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 尝试通过 app/stream 查找对应的 stream_proxy
|
||||||
|
String[] parts = oldCameraId.split("/", 2);
|
||||||
|
if (parts.length != 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StreamProxy proxy = streamProxyMapper.selectOneByAppAndStream(parts[0], parts[1]);
|
||||||
|
if (proxy != null && proxy.getCameraCode() != null && !proxy.getCameraCode().isEmpty()) {
|
||||||
|
roiMapper.updateCameraId(oldCameraId, proxy.getCameraCode());
|
||||||
|
fixed++;
|
||||||
|
log.info("[AiConfig] 修复 ROI camera_id: {} → {} (roi={})",
|
||||||
|
oldCameraId, proxy.getCameraCode(), roi.getRoiId());
|
||||||
|
} else {
|
||||||
|
log.warn("[AiConfig] ROI camera_id={} 无法找到对应的 stream_proxy 记录", oldCameraId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fixed > 0) {
|
||||||
|
log.info("[AiConfig] 启动时修复 {} 条 ROI 的 camera_id(app/stream → camera_code)", fixed);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[AiConfig] 修复 ROI camera_id 失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -100,4 +100,16 @@ public interface StreamProxyMapper {
|
|||||||
*/
|
*/
|
||||||
@Select("SELECT * FROM wvp_stream_proxy WHERE camera_code = #{cameraCode}")
|
@Select("SELECT * FROM wvp_stream_proxy WHERE camera_code = #{cameraCode}")
|
||||||
StreamProxy selectByCameraCode(@Param("cameraCode") String cameraCode);
|
StreamProxy selectByCameraCode(@Param("cameraCode") String cameraCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询 camera_code 为 NULL 或空的记录(需要回填)
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM wvp_stream_proxy WHERE camera_code IS NULL OR camera_code = ''")
|
||||||
|
List<StreamProxy> selectWithNullCameraCode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新指定记录的 camera_code
|
||||||
|
*/
|
||||||
|
@Update("UPDATE wvp_stream_proxy SET camera_code = #{cameraCode} WHERE id = #{id}")
|
||||||
|
int updateCameraCode(@Param("id") int id, @Param("cameraCode") String cameraCode);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user