diff --git a/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgTypeEnum.java b/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgCategoryEnum.java
similarity index 84%
rename from inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgTypeEnum.java
rename to inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgCategoryEnum.java
index a46bf21..ac80780 100644
--- a/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgTypeEnum.java
+++ b/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgCategoryEnum.java
@@ -1,6 +1,6 @@
package com.inspect.base.core.enums;
-public enum AlgTypeEnum {
+public enum AlgCategoryEnum {
// 表计
METER("1", "表计"),
// 外观
@@ -10,7 +10,7 @@ public enum AlgTypeEnum {
private final String code;
private final String info;
- AlgTypeEnum(String code, String info) {
+ AlgCategoryEnum(String code, String info) {
this.code = code;
this.info = info;
}
diff --git a/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java b/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java
index 730bd45..6b846b7 100644
--- a/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java
+++ b/inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java
@@ -4,11 +4,12 @@ import lombok.Getter;
@Getter
public enum DefectLevel {
- // 0=无, 1=紧急, 2=严重, 3=一般
+ // 0=无, 1=紧急, 2=严重, 3=一般, 4=轻微
NONE(0, "无"),
- URGENT(1, ""),
- SERIOUS(2, ""),
- NORMAL(3, "");
+ URGENT(1, "重大"),
+ SERIOUS(2, "严重"),
+ NORMAL(3, "一般"),
+ MINOR(4, "轻微");
private final Integer code;
private final String info;
diff --git a/inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java b/inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java
index bf563a0..ac697c5 100644
--- a/inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java
+++ b/inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java
@@ -63,7 +63,7 @@ public class IvsStreamController {
// .rtspURL("rtsp://admin:wd19216811@192.168.1.249:554/h264/ch39/main/av_stream")
// .build();
ivsVideoRtspView = new IvsVideoRtspView();
- ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.134:554/cam/realmonitor?channel=3&subtype=0");
+ ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.64:554/Streaming/Channels/102");
} else {
String rtspUrl = (String) this.redisService.redisTemplate.opsForValue().get("sys_config:RTSP_" + ivsStreamStartVo.getCameraCode());
if (StringUtils.isNotEmpty(rtspUrl)) {
@@ -116,7 +116,7 @@ public class IvsStreamController {
// .rtspURL("rtsp://admin:wd19216811@192.168.1.249:554/h264/ch39/main/av_stream")
// .build();
ivsVideoRtspView = new IvsVideoRtspView();
- ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.134:554/cam/realmonitor?channel=3&subtype=0");
+ ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.64:554/Streaming/Channels/102");
} else {
String label = java.util.UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY);
ivsVideoRtspView = ivsCommonService.postJson(
diff --git a/inspect-main/inspect-main-agg/pom.xml b/inspect-main/inspect-main-agg/pom.xml
new file mode 100644
index 0000000..3fb006b
--- /dev/null
+++ b/inspect-main/inspect-main-agg/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ com.inspect
+ inspect-main
+ 3.4.0
+
+ inspect-main-agg
+ 3.4.0
+ inspect-main-agg
+ inspect-main-agg
+
+ 1.8
+ UTF-8
+ UTF-8
+ 2.6.13
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.inspect
+ inspect-base-core
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 2.6.6
+ pom
+ import
+
+
+
+
diff --git a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/AggregationApplication.java b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/AggregationApplication.java
new file mode 100644
index 0000000..5070f9d
--- /dev/null
+++ b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/AggregationApplication.java
@@ -0,0 +1,20 @@
+package com.inspect.agg;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+
+/**
+ * 智能巡检数据汇聚系统 - 中心采集服务(区域试点)。
+ */
+@SpringBootApplication
+@EnableScheduling
+@MapperScan("com.inspect.agg.mapper")
+public class AggregationApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AggregationApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggCalendarEvent.java b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggCalendarEvent.java
new file mode 100644
index 0000000..4fa9f21
--- /dev/null
+++ b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggCalendarEvent.java
@@ -0,0 +1,28 @@
+package com.inspect.agg.calendar;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AggCalendarEvent implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String stationCode;
+ /** 仅用于展示:所属站点名称,不落业务表 */
+ private String stationName;
+ private String id;
+ private String name;
+ private String status;
+ private String taskCode;
+ private String source;
+ private LocalDateTime startTime;
+ private LocalDateTime endTime;
+}
\ No newline at end of file
diff --git a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarGenerator.java b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarGenerator.java
new file mode 100644
index 0000000..9b280a2
--- /dev/null
+++ b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarGenerator.java
@@ -0,0 +1,180 @@
+package com.inspect.agg.calendar;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.YearMonth;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import com.inspect.agg.entity.AggPatrolTask;
+import com.inspect.agg.mapper.AggPatrolTaskMapper;
+import com.inspect.base.core.enums.ExecStatus;
+import com.inspect.base.core.enums.IntervalType;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 根据汇聚方案规则生成指定月份的待执行计划。
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AggTaskCalendarGenerator {
+
+ private static final String STATE_PENDING = "5";
+
+ private final AggPatrolTaskMapper taskMapper;
+
+ public List generateMonth(String stationCode, int year, int month) {
+ YearMonth yearMonth = YearMonth.of(year, month);
+ LocalDate firstDay = yearMonth.atDay(1);
+ LocalDate lastDay = yearMonth.atEndOfMonth();
+ LocalDateTime monthStart = firstDay.atStartOfDay();
+ LocalDateTime monthEnd = lastDay.atTime(23, 59, 59);
+ LocalDateTime now = LocalDateTime.now();
+ if (monthEnd.isBefore(now)) {
+ return Collections.emptyList();
+ }
+
+ List events = new ArrayList<>();
+ List tasks = taskMapper.selectEnabledTasks(stationCode);
+ for (AggPatrolTask task : tasks) {
+ if (!StringUtils.hasText(task.getDevType())) {
+ continue;
+ }
+ try {
+ if (ExecStatus.REGULAR.getCode().equals(task.getExecutionStatus())) {
+ generateRegular(task, monthStart, monthEnd, now, events);
+ } else if (ExecStatus.PERIODIC.getCode().equals(task.getExecutionStatus())) {
+ generatePeriodic(task, monthStart, monthEnd, now, events);
+ } else if (ExecStatus.INTERVAL.getCode().equals(task.getExecutionStatus())) {
+ generateInterval(task, monthStart, monthEnd, now, events);
+ }
+ } catch (Exception e) {
+ log.warn("generate calendar failed: station={}, taskId={}, error={}",
+ task.getStationCode(), task.getTaskId(), e.getMessage());
+ }
+ }
+ events.sort((a, b) -> a.getStartTime().compareTo(b.getStartTime()));
+ return events;
+ }
+
+ private void generateRegular(AggPatrolTask task, LocalDateTime monthStart, LocalDateTime monthEnd,
+ LocalDateTime now, List events) {
+ LocalDateTime startTime = task.getFixedStartTime();
+ if (startTime != null && startTime.isAfter(now)
+ && !startTime.isBefore(monthStart) && !startTime.isAfter(monthEnd)) {
+ events.add(event(task, startTime, null));
+ }
+ }
+
+ private void generatePeriodic(AggPatrolTask task, LocalDateTime monthStart, LocalDateTime monthEnd,
+ LocalDateTime now, List events) {
+ if (task.getCycleStartTime() == null || task.getCycleEndTime() == null
+ || !StringUtils.hasText(task.getCycleExecuteTime())) {
+ return;
+ }
+ LocalDateTime start = max(monthStart, task.getCycleStartTime());
+ LocalDateTime end = min(monthEnd, task.getCycleEndTime());
+ if (end.isBefore(start)) {
+ return;
+ }
+ String[] executeTimes = task.getCycleExecuteTime().split(",");
+ if (StringUtils.hasText(task.getCycleWeek())) {
+ String[] weeks = task.getCycleWeek().split(",");
+ for (LocalDate date = start.toLocalDate(); !date.isAfter(end.toLocalDate()); date = date.plusDays(1)) {
+ for (int i = 0; i < weeks.length && i < executeTimes.length; i++) {
+ if (date.getDayOfWeek() == parseDayOfWeek(weeks[i])) {
+ addIfVisible(task, date, executeTimes[i], now, end, events);
+ }
+ }
+ }
+ } else if (StringUtils.hasText(task.getCycleMonth())) {
+ String[] monthDays = task.getCycleMonth().split(",");
+ for (LocalDate date = start.toLocalDate(); !date.isAfter(end.toLocalDate()); date = date.plusDays(1)) {
+ for (int i = 0; i < monthDays.length && i < executeTimes.length; i++) {
+ if (date.getDayOfMonth() == Integer.parseInt(monthDays[i].trim())) {
+ addIfVisible(task, date, executeTimes[i], now, end, events);
+ }
+ }
+ }
+ }
+ }
+
+ private void generateInterval(AggPatrolTask task, LocalDateTime monthStart, LocalDateTime monthEnd,
+ LocalDateTime now, List events) {
+ if (!IntervalType.BY_HOUR.getCode().equals(task.getIntervalType())
+ || task.getIntervalNumber() == null || task.getIntervalNumber() <= 0
+ || task.getIntervalStartTime() == null || task.getIntervalEndTime() == null
+ || !StringUtils.hasText(task.getIntervalExecuteTime())) {
+ return;
+ }
+ LocalDateTime start = task.getIntervalStartTime();
+ LocalDateTime end = min(monthEnd, task.getIntervalEndTime());
+ LocalTime executeTime = LocalTime.parse(task.getIntervalExecuteTime().trim());
+ LocalDateTime cursor = LocalDateTime.of(start.toLocalDate(), executeTime);
+ while (cursor.isBefore(start)) {
+ cursor = cursor.plusHours(task.getIntervalNumber());
+ }
+ while (!cursor.isAfter(end)) {
+ if (cursor.isAfter(now) && !cursor.isBefore(monthStart)) {
+ events.add(event(task, cursor, null));
+ }
+ cursor = cursor.plusHours(task.getIntervalNumber());
+ }
+ }
+
+ private void addIfVisible(AggPatrolTask task, LocalDate date, String time,
+ LocalDateTime now, LocalDateTime end,
+ List events) {
+ if (!StringUtils.hasText(time)) {
+ return;
+ }
+ LocalDateTime startTime = LocalDateTime.of(date, LocalTime.parse(time.trim()));
+ if (startTime.isAfter(now) && !startTime.isAfter(end)) {
+ events.add(event(task, startTime, null));
+ }
+ }
+
+ private AggCalendarEvent event(AggPatrolTask task, LocalDateTime startTime, LocalDateTime endTime) {
+ return AggCalendarEvent.builder()
+ .stationCode(task.getStationCode())
+ .stationName(task.getStationName())
+ .id(task.getTaskCode())
+ .taskCode(task.getTaskCode())
+ .name(task.getTaskName())
+ .status("pending")
+ .source("plan")
+ .startTime(startTime)
+ .endTime(endTime)
+ .build();
+ }
+
+ private static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
+ return a.isAfter(b) ? a : b;
+ }
+
+ private static LocalDateTime min(LocalDateTime a, LocalDateTime b) {
+ return a.isBefore(b) ? a : b;
+ }
+
+ private static DayOfWeek parseDayOfWeek(String value) {
+ String week = value == null ? "" : value.trim();
+ if ("星期一".equals(week) || "1".equals(week)) return DayOfWeek.MONDAY;
+ if ("星期二".equals(week) || "2".equals(week)) return DayOfWeek.TUESDAY;
+ if ("星期三".equals(week) || "3".equals(week)) return DayOfWeek.WEDNESDAY;
+ if ("星期四".equals(week) || "4".equals(week)) return DayOfWeek.THURSDAY;
+ if ("星期五".equals(week) || "5".equals(week)) return DayOfWeek.FRIDAY;
+ if ("星期六".equals(week) || "6".equals(week)) return DayOfWeek.SATURDAY;
+ if ("星期日".equals(week) || "7".equals(week)) return DayOfWeek.SUNDAY;
+ throw new IllegalArgumentException("不支持的星期: " + week);
+ }
+}
\ No newline at end of file
diff --git a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarService.java b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarService.java
new file mode 100644
index 0000000..fa1cf95
--- /dev/null
+++ b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarService.java
@@ -0,0 +1,340 @@
+package com.inspect.agg.calendar;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.inspect.agg.entity.AggPatrolTaskStatus;
+import com.inspect.agg.entity.AggStation;
+import com.inspect.agg.mapper.AggPatrolTaskStatusMapper;
+import com.inspect.agg.service.StationRegistry;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 汇聚计划服务:规则生成未来计划,合并实际任务状态,并使用 Redis 缓存。
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AggTaskCalendarService {
+
+ private static final String CACHE_PREFIX = "agg:calendar:v2:";
+ private static final Duration CACHE_TTL = Duration.ofHours(12);
+ private static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ private static final DateTimeFormatter DATE_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+ private static final TypeReference> EVENT_LIST_TYPE =
+ new TypeReference>() { };
+ private static final TypeReference>> STATS_LIST_TYPE =
+ new TypeReference>>() { };
+
+ private final AggTaskCalendarGenerator calendarGenerator;
+ private final AggPatrolTaskStatusMapper taskStatusMapper;
+ private final StationRegistry stationRegistry;
+ private final StringRedisTemplate redisTemplate;
+ private final ObjectMapper objectMapper;
+
+ public List