refactor(video): P2 应用层时间字段 varchar → datetime / String → LocalDateTime

SQL 类型优化(应用层生成的业务时间改为标准 datetime):
- video_ai_edge_device: last_heartbeat / updated_at
- video_ai_config_log: updated_at
- video_ai_config_snapshot: created_at
- video_ai_algorithm: sync_time
- video_ai_alert: received_at
- video_media_server: last_keepalive_time

DO 字段 String → LocalDateTime 对齐:AiEdgeDevice / AiConfigLog /
AiConfigSnapshot / AiAlgorithm / AiAlert / MediaServer

调用点适配:
- Service 层 new Date() 字符串格式化赋值统一改为 LocalDateTime.now()
- AiAlertController.edgeReport / MqttService.handleAlert 新增
  parseEventTime / parseTimestamp 私有方法,外部字符串防御性解析

保留 varchar(本批次暂不动,SIP 协议原生字符串,改动涉及 72+ 处
Mapper 与 SIP 处理器,单独迭代):
- video_device.register_time / keepalive_time
- video_device_channel.end_time / gps_time
- video_device_mobile_position.time
- video_stream_push.push_time

编译通过(mvn compile BUILD SUCCESS)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-21 12:32:23 +08:00
parent 64dcdd8d4c
commit 0ca1adf2a4
16 changed files with 78 additions and 42 deletions

View File

@@ -9,6 +9,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("video_ai_alert")
@EqualsAndHashCode(callSuper = true)
@@ -64,5 +66,5 @@ public class AiAlert extends ProjectBaseDO {
private String extraData;
@Schema(description = "接收时间")
private String receivedAt;
private LocalDateTime receivedAt;
}

View File

@@ -8,6 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("video_ai_algorithm")
@EqualsAndHashCode(callSuper = true)
@@ -40,5 +42,5 @@ public class AiAlgorithm extends TenantBaseDO {
private String globalParams;
@Schema(description = "最后同步时间")
private String syncTime;
private LocalDateTime syncTime;
}

View File

@@ -8,6 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("video_ai_config_log")
@EqualsAndHashCode(callSuper = true)
@@ -34,5 +36,5 @@ public class AiConfigLog extends ProjectBaseDO {
private String updatedBy;
@Schema(description = "操作时间")
private String updatedAt;
private LocalDateTime updatedAt;
}

View File

@@ -8,6 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("video_ai_config_snapshot")
@EqualsAndHashCode(callSuper = true)
@@ -43,5 +45,5 @@ public class AiConfigSnapshot extends ProjectBaseDO {
private String createdBy;
@Schema(description = "创建时间(业务字段,记录快照生成时间)")
private String createdAt;
private LocalDateTime createdAt;
}

View File

@@ -8,6 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("video_ai_edge_device")
@EqualsAndHashCode(callSuper = true)
@@ -25,7 +27,7 @@ public class AiEdgeDevice extends ProjectBaseDO {
private String status;
@Schema(description = "最后心跳时间")
private String lastHeartbeat;
private LocalDateTime lastHeartbeat;
@Schema(description = "运行时长(秒)")
private Long uptimeSeconds;
@@ -46,5 +48,5 @@ public class AiEdgeDevice extends ProjectBaseDO {
private String configVersion;
@Schema(description = "更新时间(业务字段)")
private String updatedAt;
private LocalDateTime updatedAt;
}

View File

@@ -13,6 +13,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
@@ -92,7 +94,7 @@ public class AiAlertController {
alert.setRoiId((String) body.get("scene_id"));
alert.setAlertType((String) body.get("algorithm_code"));
alert.setImagePath((String) body.get("snapshot_url"));
alert.setReceivedAt((String) body.get("event_time"));
alert.setReceivedAt(parseEventTime((String) body.get("event_time")));
Object conf = body.get("confidence_score");
if (conf instanceof Number) {
@@ -124,4 +126,21 @@ public class AiAlertController {
log.info("[AiAlert] Edge 告警结束: alarmId={}, durationMin={}", alarmId, minutes);
}
}
/**
* 解析外部上报的事件时间字符串为 LocalDateTime解析失败返回 null由 save() 兜底为当前时间)
* 支持格式yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd'T'HH:mm:ss
*/
private static LocalDateTime parseEventTime(String s) {
if (s == null || s.isEmpty()) {
return null;
}
try {
String normalized = s.replace('T', ' ');
return LocalDateTime.parse(normalized, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
} catch (Exception e) {
log.warn("[AiAlert] 无法解析事件时间: {}", s);
return null;
}
}
}

View File

@@ -7,6 +7,7 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
@@ -40,5 +41,5 @@ public interface AiAlgorithmMapper extends BaseMapperX<AiAlgorithm> {
}
@Update("UPDATE video_ai_algorithm SET is_active=#{isActive}, update_time=#{updateTime} WHERE id=#{id} AND deleted=0")
int updateActive(@Param("id") Long id, @Param("isActive") Integer isActive, @Param("updateTime") String updateTime);
int updateActive(@Param("id") Long id, @Param("isActive") Integer isActive, @Param("updateTime") LocalDateTime updateTime);
}

View File

@@ -9,6 +9,7 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
@@ -33,7 +34,7 @@ public interface AiEdgeDeviceMapper extends BaseMapperX<AiEdgeDevice> {
@Update("UPDATE video_ai_edge_device SET status='offline', updated_at=#{now} " +
"WHERE status='online' AND last_heartbeat < #{threshold} AND deleted=0")
int markOffline(@Param("threshold") String threshold, @Param("now") String now);
int markOffline(@Param("threshold") LocalDateTime threshold, @Param("now") LocalDateTime now);
@Update("UPDATE video_ai_edge_device SET deleted=1 WHERE device_id != #{keepDeviceId} AND deleted=0")
int deleteAllExcept(@Param("keepDeviceId") String keepDeviceId);

View File

@@ -18,6 +18,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.BiConsumer;
@Slf4j
@@ -145,8 +147,8 @@ public class MqttService {
alert.setMessage(json.getString("message"));
alert.setImagePath(json.getString("image_path"));
alert.setDurationMinutes(json.getDouble("duration_minutes"));
// timestamp → receivedAt
alert.setReceivedAt(json.getString("timestamp"));
// timestamp → receivedAt(防御性解析,失败则由 save() 兜底为当前时间)
alert.setReceivedAt(parseTimestamp(json.getString("timestamp")));
// 其余字段放入extraData
JSONObject extra = new JSONObject();
@@ -235,6 +237,22 @@ public class MqttService {
return client != null && client.isConnected();
}
/**
* 解析 MQTT 上报的时间戳字符串为 LocalDateTime解析失败返回 null由 save() 兜底为当前时间)
* 支持格式yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd'T'HH:mm:ss
*/
private static LocalDateTime parseTimestamp(String s) {
if (s == null || s.isEmpty()) {
return null;
}
try {
String normalized = s.replace('T', ' ');
return LocalDateTime.parse(normalized, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
} catch (Exception e) {
return null;
}
}
@PreDestroy
public void destroy() {
if (client != null && client.isConnected()) {

View File

@@ -14,7 +14,6 @@ import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Slf4j
@@ -27,12 +26,10 @@ public class AiAlertServiceImpl implements IAiAlertService {
@Autowired
private CosUtil cosUtil;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void save(AiAlert alert) {
if (alert.getReceivedAt() == null) {
alert.setReceivedAt(LocalDateTime.now().format(FORMATTER));
alert.setReceivedAt(LocalDateTime.now());
}
// 防止重复插入(轻量查询,不做 JOIN
if (alertMapper.countByAlertId(alert.getAlertId()) > 0) {

View File

@@ -12,7 +12,6 @@ import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,8 +29,6 @@ public class AiAlgorithmServiceImpl implements IAiAlgorithmService {
@Autowired
private IAiConfigLogService configLogService;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 预置算法定义,启动时自动校正数据库中的乱码数据
* 注意:仅包含边缘端实际实现的算法
@@ -96,7 +93,7 @@ public class AiAlgorithmServiceImpl implements IAiAlgorithmService {
@Override
public void toggleActive(Long id, Integer isActive) {
String now = LocalDateTime.now().format(FORMATTER);
LocalDateTime now = LocalDateTime.now();
algorithmMapper.updateActive(id, isActive, now);
configLogService.addLog("ALGORITHM", String.valueOf(id),
null, "{\"is_active\":" + isActive + "}", null);
@@ -109,7 +106,7 @@ public class AiAlgorithmServiceImpl implements IAiAlgorithmService {
log.warn("AI服务未启用跳过同步");
return;
}
String now = LocalDateTime.now().format(FORMATTER);
LocalDateTime now = LocalDateTime.now();
try {
RestTemplate restTemplate = new RestTemplate();
String url = aiServiceConfig.getUrl() + "/api/algorithms";

View File

@@ -11,7 +11,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Slf4j
@@ -21,8 +20,6 @@ public class AiConfigLogServiceImpl implements IAiConfigLogService {
@Autowired
private AiConfigLogMapper configLogMapper;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void addLog(String configType, String configId, String oldValue, String newValue, String updatedBy) {
AiConfigLog logEntry = new AiConfigLog();
@@ -31,7 +28,7 @@ public class AiConfigLogServiceImpl implements IAiConfigLogService {
logEntry.setOldValue(oldValue);
logEntry.setNewValue(newValue);
logEntry.setUpdatedBy(updatedBy);
logEntry.setUpdatedAt(LocalDateTime.now().format(FORMATTER));
logEntry.setUpdatedAt(LocalDateTime.now());
configLogMapper.add(logEntry);
}

View File

@@ -20,7 +20,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Slf4j
@@ -43,8 +42,6 @@ public class AiConfigSnapshotServiceImpl implements IAiConfigSnapshotService {
@Autowired
private IAiRedisConfigService redisConfigService;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public AiConfigSnapshot createSnapshot(String scopeType, String scopeId, String cameraId,
String snapshot, String changeType, String changeDesc, String createdBy) {
@@ -58,7 +55,7 @@ public class AiConfigSnapshotServiceImpl implements IAiConfigSnapshotService {
s.setChangeType(changeType);
s.setChangeDesc(changeDesc);
s.setCreatedBy(createdBy);
s.setCreatedAt(LocalDateTime.now().format(FORMATTER));
s.setCreatedAt(LocalDateTime.now());
snapshotMapper.add(s);
log.info("[AiSnapshot] 创建快照: scope={}/{}, version={}, type={}", scopeType, scopeId, s.getVersion(), changeType);
return s;

View File

@@ -14,7 +14,6 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -26,11 +25,9 @@ public class AiEdgeDeviceServiceImpl implements IAiEdgeDeviceService {
@Autowired
private AiEdgeDeviceMapper deviceMapper;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void saveOrUpdateHeartbeat(String deviceId, String payload) {
String now = LocalDateTime.now().format(FORMATTER);
LocalDateTime now = LocalDateTime.now();
try {
JSONObject json = JSON.parseObject(payload);
@@ -84,8 +81,8 @@ public class AiEdgeDeviceServiceImpl implements IAiEdgeDeviceService {
@Override
@Scheduled(fixedRate = 90000) // 每90秒检查一次
public void checkOffline() {
String now = LocalDateTime.now().format(FORMATTER);
String threshold = LocalDateTime.now().minusSeconds(90).format(FORMATTER);
LocalDateTime now = LocalDateTime.now();
LocalDateTime threshold = LocalDateTime.now().minusSeconds(90);
int count = deviceMapper.markOffline(threshold, now);
if (count > 0) {
log.warn("[AiEdgeDevice] 标记{}台设备为离线", count);

View File

@@ -10,6 +10,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.util.ObjectUtils;
import java.time.LocalDateTime;
@Schema(description = "流媒体服务信息")
@Data
@TableName("video_media_server")
@@ -95,7 +97,7 @@ public class MediaServer extends BaseDO {
private int recordAssistPort;
@Schema(description = "上次心跳时间")
private String lastKeepaliveTime;
private LocalDateTime lastKeepaliveTime;
@Schema(description = "是否是默认ZLM")
private boolean defaultServer;