Browse Source

更新 mon 模块

reghao 1 month ago
parent
commit
ce1a2e134a

+ 7 - 4
mgr/src/main/java/cn/reghao/devops/mgr/admin/controller/HomeController.java

@@ -3,6 +3,8 @@ package cn.reghao.devops.mgr.admin.controller;
 import cn.reghao.devops.mgr.admin.service.HomeViewService;
 import cn.reghao.devops.mgr.ops.srv.mon.model.ContainerReportVO;
 import cn.reghao.devops.mgr.ops.srv.mon.PrometheusService;
+import cn.reghao.devops.mgr.ops.srv.mon.model.DailyReportDTO;
+import cn.reghao.devops.mgr.ops.srv.mon.model.PillarReportDTO;
 import cn.reghao.jutil.web.WebResult;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -36,11 +38,12 @@ public class HomeController {
     @Operation(summary = "后台首页", description = "N")
     @GetMapping(value = "/api/devops/dashboard", produces = MediaType.APPLICATION_JSON_VALUE)
     @ResponseBody
-    public String dashboardData() throws Exception {
+    public String dashboardData() {
         //DashboardData dashboardData = homeViewService.getDashboardData();
-        ContainerReportVO containerReportVO = prometheusService.getReportData();
-        if (containerReportVO != null) {
-            return WebResult.success(containerReportVO);
+        DailyReportDTO pillarReportData = prometheusService.getDailyReportData();
+        //PillarReportDTO pillarReportData = prometheusService.getPillarReportData();
+        if (pillarReportData != null) {
+            return WebResult.success(pillarReportData);
         }
 
         return WebResult.failWithMsg("get dashboard data failed");

+ 11 - 2
mgr/src/main/java/cn/reghao/devops/mgr/admin/service/SiteOptionService.java

@@ -2,6 +2,8 @@ package cn.reghao.devops.mgr.admin.service;
 
 import cn.reghao.devops.mgr.admin.db.repository.SiteOptionRepo;
 import cn.reghao.devops.mgr.admin.model.po.SiteOption;
+import cn.reghao.devops.mgr.ops.srv.mon.PrometheusClientManager;
+import cn.reghao.devops.mgr.util.StringUtil;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
@@ -13,14 +15,21 @@ import org.springframework.stereotype.Service;
 @Service
 public class SiteOptionService {
     private final SiteOptionRepo siteOptionRepo;
+    private final PrometheusClientManager prometheusClientManager;
 
-    public SiteOptionService(SiteOptionRepo siteOptionRepo) {
+    public SiteOptionService(SiteOptionRepo siteOptionRepo, PrometheusClientManager prometheusClientManager) {
         this.siteOptionRepo = siteOptionRepo;
+        this.prometheusClientManager = prometheusClientManager;
     }
 
     @CacheEvict(cacheNames = "siteOptions", key = "#newSiteOption.getPropKey()")
     public void updateSiteOption(SiteOption newSiteOption) {
-        siteOptionRepo.updateSiteOption(newSiteOption);
+        if (newSiteOption.getPropKey().equals("site.devops.prometheus")) {
+            if (StringUtil.isValidStrictUrl(newSiteOption.getPropValue())) {
+                siteOptionRepo.updateSiteOption(newSiteOption);
+                prometheusClientManager.updateClient(newSiteOption.getPropValue());
+            }
+        }
     }
 
     public Page<SiteOption> getAll() {

+ 0 - 1
mgr/src/main/java/cn/reghao/devops/mgr/config/AppProperties.java

@@ -16,7 +16,6 @@ import org.springframework.context.annotation.Configuration;
 public class AppProperties {
     private String opsRoot;
     private String hostRoot;
-    private String prometheusBaseUrl;
 
     public boolean match() {
         return opsRoot.equals(hostRoot);

+ 32 - 0
mgr/src/main/java/cn/reghao/devops/mgr/config/PrometheusAvailabilityAspect.java

@@ -0,0 +1,32 @@
+package cn.reghao.devops.mgr.config;
+
+import cn.reghao.devops.mgr.ops.srv.mon.PrometheusClientManager;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author reghao
+ * @date 2026-04-02 13:05:59
+ */
+@Aspect
+@Component
+public class PrometheusAvailabilityAspect {
+    private final PrometheusClientManager clientManager;
+
+    public PrometheusAvailabilityAspect(PrometheusClientManager clientManager) {
+        this.clientManager = clientManager;
+    }
+
+    // 拦截 PrometheusService 下的所有公共方法
+    @Around("execution(* cn.reghao.devops.mgr.ops.srv.mon.PrometheusService.*(..))")
+    public Object checkAvailability(ProceedingJoinPoint joinPoint) throws Throwable {
+        if (clientManager.getClient() == null) {
+            // 这里可以根据业务返回空对象、默认值或抛异常
+            //return null;
+            throw new RuntimeException("Prometheus URL 未设置");
+        }
+        return joinPoint.proceed();
+    }
+}

+ 45 - 2
mgr/src/main/java/cn/reghao/devops/mgr/config/web/exception/ControllerErrorHandler.java

@@ -1,13 +1,21 @@
 package cn.reghao.devops.mgr.config.web.exception;
 
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
 import org.springframework.boot.web.servlet.error.ErrorController;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * 处理 controller 返回的错误, 替换默认的 BasicErrorController
@@ -21,32 +29,67 @@ import jakarta.servlet.http.HttpServletRequest;
 @Slf4j
 @Controller
 public class ControllerErrorHandler implements ErrorController {
+    private final ErrorAttributes errorAttributes;
+
+    public ControllerErrorHandler(ErrorAttributes errorAttributes) {
+        this.errorAttributes = errorAttributes;
+    }
+
     @RequestMapping("/error")
+    @ResponseBody
     public String handleError(Model model, HttpServletRequest request) {
-        int statusCode = 500;
+        ServletWebRequest webRequest = new ServletWebRequest(request);
+
+        // ErrorAttributeOptions 用于控制包含哪些信息
+        // .including(Include.MESSAGE, Include.EXCEPTION, Include.STACK_TRACE)
+        ErrorAttributeOptions options = ErrorAttributeOptions.defaults()
+                .including(ErrorAttributeOptions.Include.MESSAGE)
+                .including(ErrorAttributeOptions.Include.EXCEPTION)
+                .including(ErrorAttributeOptions.Include.STACK_TRACE);
+
+        Map<String, Object> body = errorAttributes.getErrorAttributes(webRequest, options);
+
+        // 获取原始 Throwable 对象
+        Throwable throwable = errorAttributes.getError(webRequest);
+        if (throwable != null) {
+            body.put("real_exception", throwable.getClass().getName());
+            body.put("cause", throwable.getMessage());
+        }
+
+        return JsonConverter.objectToJson(body);
+        /*int statusCode = 500;
         Object object = request.getAttribute("jakarta.servlet.error.status_code");
         if (object != null) {
             statusCode = (int) object;
         }
 
+        Map<String, Object> result = new HashMap<>();
         model.addAttribute("code", statusCode);
+        result.put("code",  statusCode);
         if (statusCode == 404) {
             model.addAttribute("msg", "页面不存在");
+            result.put("msg", "页面不存在");
         } else if (statusCode == 403) {
             model.addAttribute("msg", "没有权限");
+            result.put("msg", "没有权限");
         } else if (statusCode == 400) {
             model.addAttribute("msg", "参数错误");
+            result.put("msg", "参数错误");
         } else if (statusCode == 500) {
             model.addAttribute("msg", "服务器内部错误");
+            result.put("msg", "参数错误");
         } else {
             model.addAttribute("msg", "未知错误");
+            result.put("msg", "未知错误");
         }
 
         String prevUrl = (String) request.getAttribute("jakarta.servlet.forward.request_uri");
+        log.error("prevUrl:{}", prevUrl);
+        result.put("prevUrl", prevUrl);
         String viewPath = "/classic/error";
         if (prevUrl != null && prevUrl.startsWith("/bg")) {
             viewPath = "/admin/error";
         }
-        return viewPath;
+        return WebResult.failWithMsg(JsonConverter.objectToJson(result));*/
     }
 }

+ 5 - 2
mgr/src/main/java/cn/reghao/devops/mgr/config/web/exception/ControllerExceptionHandler.java

@@ -44,6 +44,7 @@ public class ControllerExceptionHandler {
         String uri = request.getRequestURI();
         String msg = ExceptionUtil.errorMsg(e);
         log.error("{} 接口抛出异常: {}", uri, msg);
+        e.printStackTrace();
 
         String retMsg = msg;
         int status = HttpStatus.OK.value();
@@ -77,7 +78,7 @@ public class ControllerExceptionHandler {
         ModelAndView mav = new ModelAndView();
         mav.addObject("code", ResultStatus.FAIL.getCode());
         mav.addObject("msg", retMsg);
-        Menu menu = menuService.getMenu(method, uri);
+        /*Menu menu = menuService.getMenu(method, uri);
         if (menu != null && menu.getVisible()) {
             // text/html
             String viewPath = "/classic/error";
@@ -91,6 +92,8 @@ public class ControllerExceptionHandler {
             // application/json
             mav.setView(mappingJackson2JsonView);
             return mav;
-        }
+        }*/
+        mav.setView(mappingJackson2JsonView);
+        return mav;
     }
 }

+ 15 - 1
mgr/src/main/java/cn/reghao/devops/mgr/ops/builder/controller/AppStatController.java

@@ -6,6 +6,8 @@ import cn.reghao.devops.mgr.ops.app.db.query.AppDeployQuery;
 import cn.reghao.devops.mgr.ops.builder.model.vo.AppRunning;
 import cn.reghao.devops.mgr.ops.builder.model.vo.AppRunningNode;
 import cn.reghao.devops.mgr.ops.app.service.AppRunService;
+import cn.reghao.devops.mgr.ops.srv.mon.PrometheusService;
+import cn.reghao.devops.mgr.ops.srv.mon.model.ContainerReportVO;
 import cn.reghao.jutil.jdk.web.db.PageList;
 import cn.reghao.jutil.jdk.web.result.Result;
 import cn.reghao.jutil.jdk.web.result.ResultStatus;
@@ -31,11 +33,14 @@ import java.util.List;
 public class AppStatController {
     private final AppDeployQuery appDeployQuery;
     private final AppRunService appRunService;
+    private PrometheusService prometheusService;
     private int pageSize = 10;
 
-    public AppStatController(AppDeployQuery appDeployQuery, AppRunService appRunService) {
+    public AppStatController(AppDeployQuery appDeployQuery, AppRunService appRunService,
+                             PrometheusService prometheusService) {
         this.appDeployQuery = appDeployQuery;
         this.appRunService = appRunService;
+        this.prometheusService = prometheusService;
     }
 
     @Operation(summary = "应用运行状态列表页面", description = "N")
@@ -98,4 +103,13 @@ public class AppStatController {
         String msg = String.format("正在获取 %s 的状态,请 10s 后刷新页面查看", appId);
         return WebResult.result(Result.result(ResultStatus.SUCCESS, msg));
     }
+
+    @Operation(summary = "应用当前状态", description = "N")
+    @GetMapping(value = "/mon", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String monitor(@RequestParam("appId") String appId) {
+        String containerName = "file-prod";
+        containerName = appId;
+        ContainerReportVO containerReportVO = prometheusService.getContainerReportData(containerName);
+        return containerReportVO != null ? WebResult.success(containerReportVO) : WebResult.failWithMsg("get mon data failed");
+    }
 }

+ 20 - 1
mgr/src/main/java/cn/reghao/devops/mgr/ops/machine/controller/MachineController.java

@@ -1,8 +1,11 @@
 package cn.reghao.devops.mgr.ops.machine.controller;
 
+import cn.reghao.devops.mgr.ops.machine.model.po.MachineInfo;
 import cn.reghao.devops.mgr.ops.machine.model.vo.MachineDetail;
 import cn.reghao.devops.mgr.ops.machine.service.MachineQuery;
 import cn.reghao.devops.mgr.ops.machine.service.MachineService;
+import cn.reghao.devops.mgr.ops.srv.mon.PrometheusService;
+import cn.reghao.devops.mgr.ops.srv.mon.model.ContainerReportVO;
 import cn.reghao.devops.mgr.util.SelectOption;
 import cn.reghao.jutil.jdk.web.db.PageList;
 import cn.reghao.jutil.jdk.web.result.Result;
@@ -29,10 +32,13 @@ public class MachineController {
     private int pageSize = 10;
     private final MachineQuery machineQuery;
     private final MachineService machineService;
+    private PrometheusService prometheusService;
 
-    public MachineController(MachineQuery machineQuery, MachineService machineService) {
+    public MachineController(MachineQuery machineQuery, MachineService machineService,
+                             PrometheusService prometheusService) {
         this.machineQuery = machineQuery;
         this.machineService = machineService;
+        this.prometheusService = prometheusService;
     }
 
     @Operation(summary = "机器节点页面", description = "N")
@@ -92,6 +98,19 @@ public class MachineController {
         return WebResult.success();
     }
 
+    @Operation(summary = "机器监控数据", description = "N")
+    @GetMapping(value = "/mon", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String machineMon(@RequestParam("machineId") String machineId) {
+        MachineInfo machineInfo = machineQuery.getMachineInfo(machineId);
+        if (machineInfo != null) {
+            String ipv4Address = machineInfo.getMachineIpv4();
+            ContainerReportVO containerReportVO = prometheusService.getNodeReportData(ipv4Address);
+            return containerReportVO != null ? WebResult.success(containerReportVO) : WebResult.failWithMsg("get mon data failed");
+        } else {
+            return WebResult.failWithMsg("machine not exist");
+        }
+    }
+
     @Operation(summary = "获取在线的 agent websocket 会话", description = "N")
     @GetMapping(value = "/ws", produces = MediaType.APPLICATION_JSON_VALUE)
     public String getWebSocketSessions(@RequestParam("env") String env) {

+ 3 - 0
mgr/src/main/java/cn/reghao/devops/mgr/ops/srv/mon/PrometheusAsyncClient.java

@@ -148,6 +148,9 @@ public class PrometheusAsyncClient {
                 });
     }
 
+    public void close() {
+    }
+
     public static void main(String[] args) {
         String baseUrl = "http://prometheus.iquizoo.cn";
         PrometheusAsyncClient client = new PrometheusAsyncClient(baseUrl);

+ 68 - 0
mgr/src/main/java/cn/reghao/devops/mgr/ops/srv/mon/PrometheusClientManager.java

@@ -0,0 +1,68 @@
+package cn.reghao.devops.mgr.ops.srv.mon;
+
+import cn.reghao.devops.mgr.admin.db.repository.SiteOptionRepository;
+import cn.reghao.devops.mgr.admin.model.po.SiteOption;
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author reghao
+ * @date 2026-04-02 13:03:23
+ */
+@Slf4j
+@Component
+public class PrometheusClientManager {
+    private volatile PrometheusAsyncClient client;
+    private final SiteOptionRepository siteOptionRepository;
+
+    public PrometheusClientManager(SiteOptionRepository siteOptionRepository) {
+        this.siteOptionRepository = siteOptionRepository;
+    }
+
+    /**
+     * 获取 Client,如果未初始化则返回 null
+     */
+    public PrometheusAsyncClient getClient() {
+        return client;
+    }
+
+    @PostConstruct
+    public void init() {
+        SiteOption siteOption = siteOptionRepository.findByPropKey("site.devops.prometheus");
+        if (siteOption != null) {
+            updateClient(siteOption.getPropValue());
+        }
+    }
+
+    /**
+     * 动态初始化或更新 Client
+     */
+    public synchronized void updateClient(String baseUrl) {
+        if (StringUtils.isBlank(baseUrl)) {
+            log.warn("PrometheusBaseUrl 为空,正在销毁现有 Client");
+            this.destroyClient();
+            return;
+        }
+
+        log.info("正在初始化 PrometheusAsyncClient: {}", baseUrl);
+
+        // 1. 先销毁旧实例(如果有的话)
+        this.destroyClient();
+
+        // 2. 创建新实例 (假设 PrometheusAsyncClient 是你自定义的客户端)
+        this.client = new PrometheusAsyncClient(baseUrl);
+    }
+
+    public void destroyClient() {
+        if (this.client != null) {
+            try {
+                this.client.close(); // 确保资源释放
+            } catch (Exception e) {
+                log.error("销毁 PrometheusClient 失败", e);
+            }
+            this.client = null;
+        }
+    }
+}

+ 455 - 45
mgr/src/main/java/cn/reghao/devops/mgr/ops/srv/mon/PrometheusService.java

@@ -1,7 +1,7 @@
 package cn.reghao.devops.mgr.ops.srv.mon;
 
-import cn.reghao.devops.mgr.config.AppProperties;
 import cn.reghao.devops.mgr.ops.srv.mon.model.*;
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -35,18 +35,14 @@ import java.util.stream.Collectors;
 @Service
 public class PrometheusService {
     private ObjectMapper objectMapper = new ObjectMapper();
-    private PrometheusAsyncClient promClient;
-    private Cache<String, Object> cache;
+    private final PrometheusClientManager prometheusClientManager;
+    private final Cache<String, Object> cache;
 
-    public PrometheusService(AppProperties appProperties, Cache<String, Object> cache) {
-        this.promClient = new PrometheusAsyncClient(appProperties.getPrometheusBaseUrl());
+    public PrometheusService(PrometheusClientManager prometheusClientManager, Cache<String, Object> cache) {
+        this.prometheusClientManager = prometheusClientManager;
         this.cache = cache;
     }
 
-    public PrometheusService() {
-        this.promClient = new PrometheusAsyncClient("http://prometheus.iquizoo.cn");
-    }
-
     public static Configuration getTemplateConfiguration() throws TemplateException, IOException {
         FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
         // 1. 设置模板存放路径 (通常在 resources/templates 下)
@@ -124,7 +120,7 @@ public class PrometheusService {
         );
 
         // 异步执行
-        promClient.fetchAllMetrics(tasks).thenAccept(results -> {
+        prometheusClientManager.getClient().fetchAllMetrics(tasks).thenAccept(results -> {
             processResults(results);
         }).join(); // 如果是在定时任务主线程,可以用 join 等待完成
     }
@@ -359,7 +355,7 @@ public class PrometheusService {
                 step);
     }
 
-    public void generatePillarReport() throws Exception {
+    public PillarReportDTO getPillarReportData() {
         // 1. 计算时间范围:昨天 00:00:00 到 23:59:59
         // 也可以根据需求改为:当前时间向前推 24 小时
         long end = Instant.now().getEpochSecond();
@@ -375,6 +371,7 @@ public class PrometheusService {
         }
         // 3. 计算时间戳
         end = today3AM.toEpochSecond();      // 今天凌晨 03:00:00
+        end = Instant.now().getEpochSecond();
         start = end - (24 * 3600);           // 昨天凌晨 03:00:00
 
         String step = "30m"; // 30分钟一个采样点,适合 24h 趋势图
@@ -396,7 +393,69 @@ public class PrometheusService {
         log.info("开始并行抓取 Prometheus 四大支柱数据...");
 
         // 4. 并行执行并阻塞等待结果(join)
-        Map<String, String> rawResults = promClient.fetchAllMetrics0(tasks).join();
+        Map<String, String> rawResults = prometheusClientManager.getClient().fetchAllMetrics0(tasks).join();
+        try {
+            PillarReportDTO dto = new PillarReportDTO();
+            // 设置基础信息
+            dto.setReportDate(DateTimeConverter.format(LocalDateTime.now()));
+            // 解析四大指标
+            dto.setCpuSeries(parseMatrix(rawResults.get("cpu"), true));
+            dto.setMemSeries(parseMatrix(rawResults.get("mem"), true));
+            dto.setDiskSeries(parseMatrix(rawResults.get("disk"), true));
+            dto.setNetSeries(parseMatrix(rawResults.get("net"), false)); // 网络不乘100
+
+            // 提取 X 轴标签(取任意一个结果的 values 即可)
+            String timeLabelsStr = extractTimeLabels(rawResults.get("cpu"));
+            List<String> timeLabelList = Arrays.stream(timeLabelsStr.split(","))
+                    .map(str -> str.replace("'", ""))
+                    .collect(Collectors.toList());
+            //dto.setTimeLabels(timeLabelsStr);
+            dto.setTimeLabels(timeLabelList);
+            return dto;
+        } catch (Exception e) {
+            log.error("{}", e.getMessage());
+        }
+        return null;
+    }
+
+    public void generatePillarReport() throws Exception {
+        /*// 1. 计算时间范围:昨天 00:00:00 到 23:59:59
+        // 也可以根据需求改为:当前时间向前推 24 小时
+        long end = Instant.now().getEpochSecond();
+        long start = end - (24 * 3600);
+        // 1. 获取今天的凌晨 03:00:00 (基于系统默认时区)
+        ZonedDateTime today3AM = LocalDate.now()
+                .atTime(3, 0, 0)
+                .atZone(ZoneId.systemDefault());
+        // 2. 如果当前时间还没到 3 点,LocalDate.now() 拿到的 3 点其实是“未来”,
+        //    为了保证逻辑稳健(拿已经过去的完整 24h),可以加个判断:
+        if (ZonedDateTime.now().isBefore(today3AM)) {
+            today3AM = today3AM.minusDays(1);
+        }
+        // 3. 计算时间戳
+        end = today3AM.toEpochSecond();      // 今天凌晨 03:00:00
+        start = end - (24 * 3600);           // 昨天凌晨 03:00:00
+
+        String step = "30m"; // 30分钟一个采样点,适合 24h 趋势图
+
+        // 2. 定义 PromQL 查询语句
+        String cpuQuery = "1 - avg(irate(node_cpu_seconds_total{mode='idle'}[5m])) by (instance)";
+        String memQuery = "1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)";
+        String diskQuery = "max(rate(node_disk_io_time_seconds_total[5m])) by (instance)";
+        String netQuery = "sum(irate(node_network_receive_bytes_total[5m])) by (instance) / 1024 / 1024";
+
+        // 3. 构造异步任务 Map
+        // 注意:这里调用的是 query_range 接口
+        Map<String, String> tasks = Map.of(
+                "cpu", buildRangeUrl(cpuQuery, start, end, step),
+                "mem", buildRangeUrl(memQuery, start, end, step),
+                "disk", buildRangeUrl(diskQuery, start, end, step),
+                "net", buildRangeUrl(netQuery, start, end, step)
+        );
+        log.info("开始并行抓取 Prometheus 四大支柱数据...");
+
+        // 4. 并行执行并阻塞等待结果(join)
+        Map<String, String> rawResults = prometheusClientManager.getClient().fetchAllMetrics0(tasks).join();
 
         PillarReportDTO dto = new PillarReportDTO();
         // 设置基础信息
@@ -408,7 +467,9 @@ public class PrometheusService {
         dto.setNetSeries(parseMatrix(rawResults.get("net"), false)); // 网络不乘100
 
         // 提取 X 轴标签(取任意一个结果的 values 即可)
-        dto.setTimeLabels(extractTimeLabels(rawResults.get("cpu")));
+        dto.setTimeLabels(extractTimeLabels(rawResults.get("cpu")));*/
+
+        PillarReportDTO dto = getPillarReportData();
 
         // 1. 准备数据模型 (Root Map)
         // 在模板中可以通过 ${report.reportDate} 或直接 ${reportDate} 访问
@@ -466,7 +527,8 @@ public class PrometheusService {
                     String timeLabel = Instant.ofEpochSecond(v.get(0).asLong())
                             .atZone(ZoneId.systemDefault())
                             .format(DateTimeFormatter.ofPattern("HH:mm"));
-                    timeLabels.add("'" + timeLabel + "'");
+                    //timeLabels.add("'" + timeLabel + "'");
+                    timeLabels.add(timeLabel);
                 }
             }
             labelsExtracted = true;
@@ -530,7 +592,7 @@ public class PrometheusService {
         // 1. 定义查询语句
         String cpuQuery = """
                 sum(
-                  irate(container_cpu_usage_seconds_total{name!=""}[5m])
+                  irate(container_cpu_usage_seconds_total{name!=''}[5m])
                 ) by (name, instance) * 100
                 """;
         String memQuery = """
@@ -556,8 +618,8 @@ public class PrometheusService {
         String step = "30m";
 
         // 3. 并行抓取数据(推荐异步 join,提高效率)
-        CompletableFuture<String> cpuFuture = promClient.queryRange(cpuQuery, start, end, step);
-        CompletableFuture<String> memFuture = promClient.queryRange(memQuery, start, end, step);
+        CompletableFuture<String> cpuFuture = prometheusClientManager.getClient().queryRange(cpuQuery, start, end, step);
+        CompletableFuture<String> memFuture = prometheusClientManager.getClient().queryRange(memQuery, start, end, step);
         String cpuJson = cpuFuture.join();
         String memJson = memFuture.join();
         // 4. 解析数据
@@ -600,10 +662,10 @@ public class PrometheusService {
         String step = "30m";
 
         // 3. 并行抓取
-        CompletableFuture<String> cpuFuture = promClient.queryRange(cpuQuery, start, end, step);
-        CompletableFuture<String> cpuOldFuture = promClient.queryRange(cpuQueryOld, start, end, step);
-        CompletableFuture<String> memFuture = promClient.queryRange(memQuery, start, end, step);
-        CompletableFuture<String> memOldFuture = promClient.queryRange(memQueryOld, start, end, step);
+        CompletableFuture<String> cpuFuture = prometheusClientManager.getClient().queryRange(cpuQuery, start, end, step);
+        CompletableFuture<String> cpuOldFuture = prometheusClientManager.getClient().queryRange(cpuQueryOld, start, end, step);
+        CompletableFuture<String> memFuture = prometheusClientManager.getClient().queryRange(memQuery, start, end, step);
+        CompletableFuture<String> memOldFuture = prometheusClientManager.getClient().queryRange(memQueryOld, start, end, step);
 
         CompletableFuture.allOf(cpuFuture, cpuOldFuture, memFuture, memOldFuture).join();
 
@@ -634,7 +696,8 @@ public class PrometheusService {
         // Caffeine 的 get 方法天然支持并发锁,防止击穿
         Object result = cache.get(cacheKey, key -> {
             try {
-                return getContainerReportData();
+                String containerName = "file-prod";
+                return null;
             } catch (Exception e) {
                 log.error("Failed to generate report", e);
                 return null;
@@ -643,33 +706,70 @@ public class PrometheusService {
         return (ContainerReportVO) result;
     }
 
-    public ContainerReportVO getContainerReportData() throws Exception {
-        // 1. 定义查询语句 (修正后的 PromQL)
-        String cpuQuery = "sum(irate(container_cpu_usage_seconds_total{name=~'.*-prod'}[5m])) by (name, instance) * 100";
-        String cpuQueryOld = "sum(irate(container_cpu_usage_seconds_total{name=~'.*-prod'}[5m] offset 1d)) by (name, instance) * 100";
-        String memQuery = "sum(container_memory_working_set_bytes{name=~'.*-prod'}) by (name, instance) / 1024 / 1024";
-        String memQueryOld = "sum(container_memory_working_set_bytes{name=~'.*-prod' } offset 1d) by (name, instance) / 1024 / 1024";
+    public ContainerReportVO getNodeReportData(String ipv4Address) {
+        String instance = ipv4Address;
+        // 定义查询任务
+        Map<String, String> queryMap = Map.of(
+                "cpuQuery", "sum(irate(container_cpu_usage_seconds_total{instance=~'^" + instance  + ":.*'}[5m])) by (name, instance) * 100",
+                "cpuQueryOld", "sum(irate(container_cpu_usage_seconds_total{instance=~'^" + instance  + ":.*'}[5m] offset 1d)) by (name, instance) * 100",
+                "memQuery", "sum(container_memory_working_set_bytes{instance=~'^" + instance  + ":.*'}) by (name, instance) / 1024 / 1024",
+                "memQueryOld", "sum(container_memory_working_set_bytes{instance=~'^" + instance  + ":.*' } offset 1d) by (name, instance) / 1024 / 1024"
+        );
 
-        // 2. 时间范围计算 (今日凌晨 03:00)
-        ZonedDateTime today3AM = LocalDate.now().atTime(3, 0, 0).atZone(ZoneId.systemDefault());
-        if (ZonedDateTime.now().isBefore(today3AM)) today3AM = today3AM.minusDays(1);
+        try {
+            ContainerReportVO containerReportVO = getReportData(queryMap);
+            return containerReportVO;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
 
-        long end = today3AM.toEpochSecond();
+    public ContainerReportVO getContainerReportData(String containerName) {
+        // 定义查询任务
+        Map<String, String> queryMap = Map.of(
+                "cpuQuery", "sum(irate(container_cpu_usage_seconds_total{name='" + containerName  + "'}[5m])) by (name, instance) * 100",
+                "cpuQueryOld", "sum(irate(container_cpu_usage_seconds_total{name='" + containerName  + "'}[5m] offset 1d)) by (name, instance) * 100",
+                "memQuery", "sum(container_memory_working_set_bytes{name='" + containerName  + "'}) by (name, instance) / 1024 / 1024",
+                "memQueryOld", "sum(container_memory_working_set_bytes{name='" + containerName  + "' } offset 1d) by (name, instance) / 1024 / 1024",
+                "throttleQuery", "sum(increase(container_cpu_cfs_throttled_periods_total{name='"  + containerName  +  "'}[5m])) by (name, instance)"
+        );
+
+        try {
+            ContainerReportVO containerReportVO = getReportData(queryMap);
+            return containerReportVO;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private ContainerReportVO getReportData(Map<String, String> queryMap) throws Exception {
+        // 2. 时间范围计算(过去 24h)
+        long end = Instant.now().getEpochSecond();
         long start = end - (24 * 3600);
         String step = "30m";
 
         // 3. 并行抓取
-        CompletableFuture<String> cpuFuture = promClient.queryRange(cpuQuery, start, end, step);
-        CompletableFuture<String> cpuOldFuture = promClient.queryRange(cpuQueryOld, start, end, step);
-        CompletableFuture<String> memFuture = promClient.queryRange(memQuery, start, end, step);
-        CompletableFuture<String> memOldFuture = promClient.queryRange(memQueryOld, start, end, step);
-        CompletableFuture.allOf(cpuFuture, cpuOldFuture, memFuture, memOldFuture).join();
+        CompletableFuture<String> cpuFuture = prometheusClientManager.getClient().queryRange(queryMap.get("cpuQuery"), start, end, step);
+        CompletableFuture<String> cpuOldFuture = prometheusClientManager.getClient().queryRange(queryMap.get("cpuQueryOld"), start, end, step);
+        CompletableFuture<String> memFuture = prometheusClientManager.getClient().queryRange(queryMap.get("memQuery"), start, end, step);
+        CompletableFuture<String> memOldFuture = prometheusClientManager.getClient().queryRange(queryMap.get("memQueryOld"), start, end, step);
+        CompletableFuture<String> throttleQuery = null;
+        if (queryMap.get("throttleQuery") != null) {
+            throttleQuery = prometheusClientManager.getClient().queryRange(queryMap.get("throttleQuery"), start, end, step);
+            CompletableFuture.allOf(cpuFuture, cpuOldFuture, memFuture, memOldFuture, throttleQuery).join();
+        } else {
+            CompletableFuture.allOf(cpuFuture, cpuOldFuture, memFuture, memOldFuture).join();
+        }
 
         // 4. 解析原始数据 (假设解析出的结构依然是 Map<Instance, Map<Container, List<Double>>>)
         Map<String, Map<String, List<Double>>> cpuT = parseCpuJson(cpuFuture.get());
         Map<String, Map<String, List<Double>>> cpuY = parseCpuJson(cpuOldFuture.get());
         Map<String, Map<String, List<Double>>> memT = parseMemJson(memFuture.get());
         Map<String, Map<String, List<Double>>> memY = parseMemJson(memOldFuture.get());
+        Map<String, Map<String, List<Double>>> cpuThrottleMap = new HashMap<>();
+        if (throttleQuery != null) {
+            cpuThrottleMap = parseCpuJson(throttleQuery.get());
+        }
 
         // 5. 核心:按实例(Instance)维度聚合数据
         // 获取所有出现过的实例名并去重
@@ -678,9 +778,9 @@ public class PrometheusService {
         allInstanceNames.addAll(cpuY.keySet());
         allInstanceNames.addAll(memT.keySet());
         allInstanceNames.addAll(memY.keySet());
+        allInstanceNames.addAll(cpuThrottleMap.keySet());
 
         List<HostData> instanceList = new ArrayList<>();
-
         for (String instName : allInstanceNames) {
             HostData instData = new HostData();
             instData.setName(instName);
@@ -697,6 +797,11 @@ public class PrometheusService {
             memGroup.setYesterday(memY.getOrDefault(instName, new HashMap<>()));
             instData.setMem(memGroup);
 
+            // 组装 cpuThrottle 组
+            MetricGroup cpuThrottleGroup = new MetricGroup();
+            cpuThrottleGroup.setToday(cpuThrottleMap.getOrDefault(instName, new HashMap<>()));
+            instData.setCpuThrottle(cpuThrottleGroup);
+
             instanceList.add(instData);
         }
 
@@ -704,7 +809,6 @@ public class PrometheusService {
         ContainerReportVO report = new ContainerReportVO();
         report.setTimeLabels(getCpuTimeLabels(cpuFuture.get()));
         report.setInstances(instanceList);
-
         return report;
     }
 
@@ -726,7 +830,7 @@ public class PrometheusService {
         );
 
         // 异步执行
-        promClient.fetchAllMetrics(tasks).thenAccept(results -> {
+        prometheusClientManager.getClient().fetchAllMetrics(tasks).thenAccept(results -> {
             processResults0(results);
         }).join(); // 如果是在定时任务主线程,可以用 join 等待完成
     }
@@ -748,11 +852,317 @@ public class PrometheusService {
         }
     }
 
+    public DailyReportDTO getDailyReportData() {
+        // 定义查询任务
+        Map<String, String> tasks = new HashMap<>();
+        // 使用 put 方法依次添加原有的 PromQL 任务
+        tasks.put("container_cpu", """
+        topk(10,\s
+          sum(
+            label_replace(
+              increase(container_cpu_cfs_throttled_seconds_total[24h]),
+              "instance", "$1", "instance", "([^:]+):.*"
+            )
+          ) by (name, instance)
+        )
+        """);
+
+        tasks.put("container_mem", """
+        topk(10,\s
+          avg by (name, instance) (
+            label_replace(
+              (container_memory_working_set_bytes{name!=""} / container_spec_memory_limit_bytes > 0) * 100,
+              "instance", "$1", "instance", "([^:]+):.*"
+            )
+          )
+        )
+        """);
+
+        tasks.put("node_disk", """
+        avg_over_time(
+            label_replace(
+              irate(node_disk_io_time_seconds_total[10m]),
+              "instance", "$1", "instance", "([^:]+):.*"
+            )[24h:1m]
+          )
+        """);
+
+        tasks.put("node_inode", """
+        topk(10, (1 - node_filesystem_files_free / node_filesystem_files) * 100)
+        """);
+
+        tasks.put("node_fd", """
+        topk(10, (node_filefd_allocated / node_filefd_maximum) * 100)
+        """);
+
+        tasks.put("node_disk_usage", """
+        topk(10, (1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100)
+        """);
+
+        // 24h 内 TCP 正常连接的最大并发数
+        tasks.put("net_tcp_est_max", """
+        max_over_time(label_replace(node_netstat_Tcp_CurrEstab, "state", "ESTABLISHED", "", "")[24h:])
+        """);
+
+        // 24h 内 TCP 等待关闭连接的最大堆积数
+        tasks.put("net_tcp_tw_max", """
+        max_over_time(label_replace(node_sockstat_TCP_tw, "state", "TIME_WAIT", "", "")[24h:])
+        """);
+
+        // TCP 全连接队列溢出 (ListenOverflows) - 24h 增量
+        tasks.put("net_tcp_overflow", """
+        topk(10, increase(node_netstat_TcpExt_ListenOverflows[24h]))
+        """);
+
+        // TCP 丢弃计数 (TcpExt_ListenDrops) - 24h 增量
+        tasks.put("net_tcp_drops", """
+        topk(10, increase(node_netstat_TcpExt_ListenDrops[24h]))
+        """);
+
+        // 新增的三个任务
+        tasks.put("node_oom", "increase(node_vmstat_oom_kill[24h])");
+        tasks.put("node_clock", "abs(node_timex_offset_seconds)");
+        tasks.put("node_ro_fs", "node_filesystem_readonly{mountpoint='/'}");
+        // 逻辑:计算 node 级别每秒上下文切换次数
+        tasks.put("node_context_switch", """
+                topk(10, avg_over_time(rate(node_context_switches_total[5m])[24h:1m]))
+                """);
+
+        /*Map<String, String> tasks = Map.of(
+                "container_cpu", """
+                        topk(10,\s
+                          sum(
+                            label_replace(
+                              increase(container_cpu_cfs_throttled_seconds_total[24h]),
+                              "instance", "$1", "instance", "([^:]+):.*"
+                            )
+                          ) by (name, instance)
+                        )
+                        """,
+                "container_mem", """
+                        topk(10,\s
+                          avg by (name, instance) (
+                            label_replace(
+                              (container_memory_working_set_bytes{name!=""} / container_spec_memory_limit_bytes > 0) * 100,
+                              "instance", "$1", "instance", "([^:]+):.*"
+                            )
+                          )
+                        )
+                        """,
+                "node_disk", """
+                        avg_over_time(
+                            label_replace(
+                              irate(node_disk_io_time_seconds_total[10m]),
+                              "instance", "$1", "instance", "([^:]+):.*"
+                            )[24h:1m]
+                          )
+                        """,
+                "node_inode", """
+                        topk(10, (1 - node_filesystem_files_free / node_filesystem_files) * 100)
+                        """,
+                "node_fd", """
+                        topk(10, (node_filefd_allocated / node_filefd_maximum) * 100)
+                        """,
+                "node_disk_usage", """
+                        topk(10, (1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100)
+                        """,
+                // 24h 内 TCP 正常连接的最大并发数
+                "net_tcp_est_max", """
+                        max_over_time(label_replace(node_netstat_Tcp_CurrEstab, "state", "ESTABLISHED", "", "")[24h:])
+                        """,
+                // 24h 内 TCP 等待关闭连接的最大堆积数
+                "net_tcp_tw_max", """
+                        max_over_time(label_replace(node_sockstat_TCP_tw, "state", "TIME_WAIT", "", "")[24h:])
+                        """,
+                // TCP 全连接队列溢出 (ListenOverflows) - 24h 增量
+                // 如果这个值 > 0,说明应用 backlog 满了,正在丢弃新连接
+                "net_tcp_overflow", """
+                        topk(10, increase(node_netstat_TcpExt_ListenOverflows[24h]))
+                        """,
+                // TCP 丢弃计数 (TcpExt_ListenDrops) - 24h 增量
+                "net_tcp_drops", """
+                        topk(10, increase(node_netstat_TcpExt_ListenDrops[24h]))
+                        """
+        );*/
+
+        return prometheusClientManager.getClient()
+                .fetchAllMetrics(tasks)
+                .thenApply(this::processResults1) // 这里 processResults1 需要改为返回 DailyReportDTO
+                .join();
+    }
+
+    public DailyReportDTO processResults00(Map<String, String> results) {
+        DailyReportDTO report = new DailyReportDTO();
+
+        try {
+            report.setCpuThrottled(parsePrometheusJson(results.get("container_cpu"), "name"));
+            report.setMemRisk(parsePrometheusJson(results.get("container_mem"), "name"));
+            report.setDiskIo(parsePrometheusJson(results.get("node_disk"), "instance"));
+
+            // 简单的状态判定逻辑
+            if (report.getMemRisk().stream().anyMatch(i -> i.getValue() > 90)) {
+                report.setStatusSummary("存在内存风险点");
+            }
+        } catch (Exception e) {
+            log.error("解析监控数据失败", e);
+        }
+
+        return report;
+    }
+
+    // 定义全局风险阈值
+    private static final double MEM_RISK_THRESHOLD = 30.0;      // 内存超过85%需注意
+    private static final double CPU_THROTTLE_THRESHOLD = 1000.0; // 24h节流超过10秒需注意
+    private static final double DISK_IO_THRESHOLD = 1.0;      // IO Wait超过50ms需注意
+    private static final double INODE_RISK_THRESHOLD = 10.0;    // Inode 超过 80% 需注意
+    private static final double FD_RISK_THRESHOLD = 10.0; // 句柄占用超过 80% 需注意
+    private static final double DISK_USAGE_THRESHOLD = 80.0; // 磁盘空间超过 85% 需注意
+    private static final double TCP_EST_THRESHOLD = 5000.0; // 单机并发超过5000需注意(根据业务调整)
+    private static final double TCP_TW_THRESHOLD = 50.0; // TIME_WAIT 超过 5000 需注意
+    private static final double NET_DROP_THRESHOLD = 1.0;   // 只要有 1 个溢出或丢弃就需注意
+    private static final double OOM_KILL_THRESHOLD = 1.0;      // 24h 内发生过 OOM
+    private static final double CLOCK_SKEW_THRESHOLD = 0.5;    // 时钟偏移超过 500ms
+    private static final double READONLY_FS_THRESHOLD = 1.0;   // 存在只读文件系统
+    private static final double ZOMBIE_PROCS_THRESHOLD = 5.0;  // 僵尸进程过多
+    // 假设是 8 核机器,总切换数超过 50,000 需注意
+    private static final double CONTEXT_SWITCH_THRESHOLD = 50000.0;
+
+    public DailyReportDTO processResults1(Map<String, String> results) {
+        DailyReportDTO report = new DailyReportDTO();
+        try {
+            // 1. 解析并筛选 CPU 节流 (只保留显著受限的容器)
+            report.setCpuThrottled(
+                    parseAndFilter(results.get("container_cpu"), "name", CPU_THROTTLE_THRESHOLD)
+            );
+
+            // 2. 解析并筛选 内存风险 (只保留接近 Limit 的容器)
+            report.setMemRisk(
+                    parseAndFilter(results.get("container_mem"), "name", MEM_RISK_THRESHOLD)
+            );
+
+            // 3. 解析并筛选 磁盘 IO (只保留高负载节点)
+            report.setDiskIo(
+                    parseAndFilter(results.get("node_disk"), "instance", DISK_IO_THRESHOLD)
+            );
+
+            report.setInodeRisk(
+                    parseAndFilter(results.get("node_inode"), "instance", INODE_RISK_THRESHOLD)
+            );
+
+            // 2. 新增:解析并筛选文件句柄风险
+            report.setFdRisk(
+                    parseAndFilter(results.get("node_fd"), "instance", FD_RISK_THRESHOLD)
+            );
+
+            report.setDiskUsageRisk(
+                    parseAndFilter(results.get("node_disk_usage"), "instance", DISK_USAGE_THRESHOLD)
+            );
+
+            // 解析 24h TCP EST 峰值
+            report.setNetEstMax(
+                    parseAndFilter(results.get("net_tcp_est_max"), "state", TCP_EST_THRESHOLD)
+            );
+
+            // 解析并筛选 24h TCP TIME_WAIT 风险
+            report.setNetTwMax(
+                    parseAndFilter(results.get("net_tcp_tw_max"), "state", TCP_TW_THRESHOLD)
+            );
+
+            // 筛选全连接队列溢出和丢弃
+            report.setNetOverflows(
+                    parseAndFilter(results.get("net_tcp_overflow"), "instance", NET_DROP_THRESHOLD)
+            );
+            report.setNetDrops(
+                    parseAndFilter(results.get("net_tcp_drops"), "instance", NET_DROP_THRESHOLD)
+            );
+
+            if (!report.getMemRisk().isEmpty()) {
+                report.getAdvices().put("memRisk", "内存风险排查流程");
+            }
+
+            // 2. 解析并筛选上下文切换风险
+            report.setContextSwitchRisk(
+                    parseAndFilter(results.get("node_context_switch"), "instance", CONTEXT_SWITCH_THRESHOLD)
+            );
+
+            // 计算汇总状态
+            long totalIssues = report.getCpuThrottled().size() +
+                    report.getMemRisk().size() +
+                    report.getDiskIo().size() +
+                    report.getInodeRisk().size() +
+                    report.getFdRisk().size() +
+                    report.getDiskUsageRisk().size() +
+                    report.getNetEstMax().size() +
+                    report.getNetTwMax().size() +
+                    report.getNetOverflows().size() +
+                    report.getNetDrops().size();
+
+            report.setStatusSummary(totalIssues > 0 ? "发现 " + totalIssues + " 项待处理异常" : "所有指标正常");
+        } catch (Exception e) {
+            log.error("{}", e.getMessage());
+        }
+
+        return report;
+    }
+
+    private List<DailyReportDTO.MetricItem> parseAndFilter(String json, String nameLabel, double threshold) throws Exception {
+        List<DailyReportDTO.MetricItem> filteredItems = new ArrayList<>();
+        JsonNode resultNode = objectMapper.readTree(json).path("data").path("result");
+
+        if (resultNode.isArray()) {
+            for (JsonNode node : resultNode) {
+                double val = node.path("value").get(1).asDouble();
+
+                // 核心逻辑:只有超过阈值的才加入报告
+                if (val >= threshold) {
+                    String name = node.path("metric").path(nameLabel).asText("unknown");
+                    String instance = node.path("metric").path("instance").asText("unknown");
+                    String name1 = String.format("%s_%s", instance, name);
+                    String job = node.path("metric").path("job").asText("unknown");
+                    if ("node-exporter".equals(job)) {
+                        name1 = instance.split(":")[0];
+                        instance = name1;
+                    }
+
+                    filteredItems.add(new DailyReportDTO.MetricItem(name1, instance, Math.round(val * 100.0) / 100.0, 0.0));
+                }
+            }
+        }
+        // 按数值倒序排列,最重要的放在最上面
+        filteredItems.sort((a, b) -> b.getValue().compareTo(a.getValue()));
+        return filteredItems;
+    }
+
+    private List<DailyReportDTO.MetricItem> parsePrometheusJson(String json, String nameLabel) throws Exception {
+        List<DailyReportDTO.MetricItem> items = new ArrayList<>();
+        JsonNode root = objectMapper.readTree(json);
+        JsonNode resultNode = root.path("data").path("result");
+
+        if (resultNode.isArray()) {
+            for (JsonNode node : resultNode) {
+                JsonNode metric = node.path("metric");
+                // Prometheus 返回的 value 是 [timestamp, "value"]
+                JsonNode valueArray = node.path("value");
+                if (valueArray.isMissingNode()) {
+                    // 如果是 range query, 取 values 数组的最后一个
+                    valueArray = node.path("values").get(node.path("values").size() - 1);
+                }
+
+                String name = metric.path(nameLabel).asText("unknown");
+                String instance = metric.path("instance").asText("unknown");
+                Double val = valueArray.get(1).asDouble();
+
+                items.add(new DailyReportDTO.MetricItem(name, instance, Math.round(val * 100.0) / 100.0, 0.0));
+            }
+        }
+        return items;
+    }
+
     public static void main(String[] args) throws Exception {
-        PrometheusService prometheusService = new PrometheusService();
-        prometheusService.generateContainerReport1();
-        prometheusService.generateContainerReport();
-        prometheusService.generatePillarReport();
-        prometheusService.generateDailyReport();
+        //PrometheusService prometheusService = new PrometheusService(appProperties, null);
+        /*prometheusService.generateContainerReport1();
+        prometheusService.generateContainerReport();*/
+        //prometheusService.generatePillarReport();
+        //prometheusService.generateDailyReport();
     }
 }

+ 46 - 0
mgr/src/main/java/cn/reghao/devops/mgr/ops/srv/mon/model/DailyReportDTO.java

@@ -0,0 +1,46 @@
+package cn.reghao.devops.mgr.ops.srv.mon.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2026-04-02 14:53:03
+ */
+@Data
+public class DailyReportDTO {
+    private String lastUpdateTime = LocalDate.now().toString();
+    private String statusSummary = "正常";
+    private int totalNodes = 10;
+    private String checkDuration = "1.2s";
+    private String riskLevel = "Medium";
+
+    private List<MetricItem> cpuThrottled = new ArrayList<>();
+    private List<MetricItem> memRisk = new ArrayList<>();
+    private List<MetricItem> diskIo = new ArrayList<>();
+    private List<MetricItem> inodeRisk = new ArrayList<>();
+    private List<MetricItem> fdRisk = new ArrayList<>();
+    private List<MetricItem> diskUsageRisk = new ArrayList<>();
+    private List<MetricItem> netEstMax = new ArrayList<>();
+    private List<MetricItem> netTwMax = new ArrayList<>();
+    private List<MetricItem> netOverflows = new ArrayList<>();
+    private List<MetricItem> netDrops = new ArrayList<>();
+    private List<MetricItem> contextSwitchRisk = new ArrayList<>();
+
+    private Map<String, String> advices = new HashMap<>();
+
+    @Data
+    @AllArgsConstructor
+    public static class MetricItem {
+        private String name;      // 容器名或实例名
+        private String instance;  // 宿主机
+        private Double value;     // 当前数值
+        private Double compare;   // 环比增长 (可选)
+    }
+}

+ 1 - 0
mgr/src/main/java/cn/reghao/devops/mgr/ops/srv/mon/model/HostData.java

@@ -11,4 +11,5 @@ public class HostData {
     private String name; // 对应 instance
     private MetricGroup cpu;
     private MetricGroup mem;
+    private MetricGroup cpuThrottle;
 }

+ 4 - 3
mgr/src/main/java/cn/reghao/devops/mgr/ops/srv/mon/model/PillarReportDTO.java

@@ -2,6 +2,7 @@ package cn.reghao.devops.mgr.ops.srv.mon.model;
 
 import lombok.Data;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -13,15 +14,15 @@ import java.util.Map;
 @Data
 public class PillarReportDTO {
     private String reportDate;      // 报告日期
+    private String statusSummary = "系统整体运行平稳,未发现超标异常。";
     private String startTime;       // 统计起始时间
     private String endTime;         // 统计结束时间
-    private String timeLabels;      // ECharts X轴: "'00:00','00:30'..."
+    private List<String> timeLabels = new ArrayList<>();      // ECharts X轴: "'00:00','00:30'..."
+    private String timeLabels1;      // ECharts X轴: "'00:00','00:30'..."
 
     // Key: Instance IP, Value: 数据序列 [0.1, 0.5, ...]
     private Map<String, List<Double>> cpuSeries = new HashMap<>();
     private Map<String, List<Double>> memSeries = new HashMap<>();
     private Map<String, List<Double>> diskSeries = new HashMap<>();
     private Map<String, List<Double>> netSeries = new HashMap<>();
-
-    private String statusSummary = "系统整体运行平稳,未发现超标异常。";
 }

+ 11 - 0
mgr/src/main/java/cn/reghao/devops/mgr/util/StringUtil.java

@@ -65,4 +65,15 @@ public class StringUtil {
         //converted = matcher.replaceAll("<a href=\"$1\">$1</a>");
         return converted;
     }
+
+    // 正则说明:
+    // ^https?://          -> 以 http:// 或 https:// 开头
+    // [a-zA-Z0-9][-a-zA-Z0-9]{0,62} -> 域名首位字符及后续(最长63字符)
+    // (\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+ -> 匹配 .com, .com.cn 等后缀
+    // $                   -> 严格匹配结尾,确保后面没有多余字符(如 /)
+    private static final String URL_PATTERN = "^https?://[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$";
+    public static boolean isValidStrictUrl(String url) {
+        if (url == null) return false;
+        return Pattern.matches(URL_PATTERN, url);
+    }
 }