Sfoglia il codice sorgente

Merge branch 'master' of http://60.171.161.56:20000/tjf/deviceManage

wangmengwei 1 giorno fa
parent
commit
9c30b6a704

+ 2 - 2
ruoyi-admin/src/main/resources/application.yml

@@ -19,8 +19,8 @@ spring:
     # 国际化资源文件路径
     basename: i18n/messages
   profiles:
-          active: druid
-#          active: prod
+#          active: druid
+          active: prod
   # 文件上传
   servlet:
     multipart:

+ 26 - 26
ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java

@@ -5,28 +5,30 @@ import java.math.RoundingMode;
 
 /**
  * 精确的浮点数运算
- * 
+ *
  * @author ruoyi
  */
-public class Arith
-{
+public class Arith {
 
-    /** 默认除法运算精度 */
+    /**
+     * 默认除法运算精度
+     */
     private static final int DEF_DIV_SCALE = 10;
 
-    /** 这个类不能实例化 */
-    private Arith()
-    {
+    /**
+     * 这个类不能实例化
+     */
+    private Arith() {
     }
 
     /**
      * 提供精确的加法运算。
+     *
      * @param v1 被加数
      * @param v2 加数
      * @return 两个参数的和
      */
-    public static double add(double v1, double v2)
-    {
+    public static double add(double v1, double v2) {
         BigDecimal b1 = new BigDecimal(Double.toString(v1));
         BigDecimal b2 = new BigDecimal(Double.toString(v2));
         return b1.add(b2).doubleValue();
@@ -34,12 +36,12 @@ public class Arith
 
     /**
      * 提供精确的减法运算。
+     *
      * @param v1 被减数
      * @param v2 减数
      * @return 两个参数的差
      */
-    public static double sub(double v1, double v2)
-    {
+    public static double sub(double v1, double v2) {
         BigDecimal b1 = new BigDecimal(Double.toString(v1));
         BigDecimal b2 = new BigDecimal(Double.toString(v2));
         return b1.subtract(b2).doubleValue();
@@ -47,6 +49,7 @@ public class Arith
 
     /**
      * 提供精确的乘法运算。
+     *
      * @param v1 被乘数
      * @param v2 乘数
      * @return 两个参数的积
@@ -61,34 +64,32 @@ public class Arith
     /**
      * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
      * 小数点以后10位,以后的数字四舍五入。
+     *
      * @param v1 被除数
      * @param v2 除数
      * @return 两个参数的商
      */
-    public static double div(double v1, double v2)
-    {
+    public static double div(double v1, double v2) {
         return div(v1, v2, DEF_DIV_SCALE);
     }
 
     /**
      * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
      * 定精度,以后的数字四舍五入。
-     * @param v1 被除数
-     * @param v2 除数
+     *
+     * @param v1    被除数
+     * @param v2    除数
      * @param scale 表示表示需要精确到小数点以后几位。
      * @return 两个参数的商
      */
-    public static double div(double v1, double v2, int scale)
-    {
-        if (scale < 0)
-        {
+    public static double div(double v1, double v2, int scale) {
+        if (scale < 0) {
             throw new IllegalArgumentException(
                     "The scale must be a positive integer or zero");
         }
         BigDecimal b1 = new BigDecimal(Double.toString(v1));
         BigDecimal b2 = new BigDecimal(Double.toString(v2));
-        if (b1.compareTo(BigDecimal.ZERO) == 0)
-        {
+        if (b1.compareTo(BigDecimal.ZERO) == 0) {
             return BigDecimal.ZERO.doubleValue();
         }
         return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
@@ -96,14 +97,13 @@ public class Arith
 
     /**
      * 提供精确的小数位四舍五入处理。
-     * @param v 需要四舍五入的数字
+     *
+     * @param v     需要四舍五入的数字
      * @param scale 小数点后保留几位
      * @return 四舍五入后的结果
      */
-    public static double round(double v, int scale)
-    {
-        if (scale < 0)
-        {
+    public static double round(double v, int scale) {
+        if (scale < 0) {
             throw new IllegalArgumentException(
                     "The scale must be a positive integer or zero");
         }

+ 0 - 369
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/AdvancedSystemMonitor.java

@@ -1,369 +0,0 @@
-package com.ruoyi.framework.web.domain;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class AdvancedSystemMonitor {
-
-    /**
-     * CPU插槽 0 温度: 48.5°C
-     * CPU插槽 1 温度: 47.2°C
-     * CPU核心 Socket0-Core0 频率: 3.20 GHz
-     * CPU核心 Socket0-Core1 频率: 3.18 GHz
-     * CPU核心 Socket1-Core24 频率: 2.95 GHz
-     * CPU核心 Socket1-Core25 频率: 2.97 GHz
-     * <p>
-     * GPU #0 信息:
-     * 索引: 0
-     * 名称: NVIDIA GeForce RTX 3090
-     * 温度: 62°C
-     * 使用率: 24%
-     * 显存使用: 3456 MB
-     * 总显存: 24564 MB
-     * 功耗: 350.0 W
-     * 功耗限制: 400.0 W
-     * 图形时钟: 1860 MHz
-     * 显存时钟: 1219 MHz
-     * <p>
-     * GPU #1 信息:
-     * 索引: 1
-     * 名称: NVIDIA Tesla V100
-     * 温度: 58°C
-     * 使用率: 45%
-     * 显存使用: 16384 MB
-     * 总显存: 32768 MB
-     * 功耗: 250.0 W
-     * 功耗限制: 300.0 W
-     * 图形时钟: 1380 MHz
-     * 显存时钟: 877 MHz
-     * <p>
-     * 网络速率 (eth0):
-     * 下行: 1.23 Gbps
-     * 上行: 345.67 Mbps
-     */
-    public static String advancedSystem() {
-        try {
-            // 1. 获取所有CPU插槽的温度
-            Map<Integer, Double> cpuTemps = getCpuTemperatures();
-            cpuTemps.forEach((socket, temp) ->
-                    System.out.printf("CPU插槽 %d 温度: %.1f°C\n", socket, temp)
-            );
-
-            // 2. 获取所有CPU核心的实时频率
-            Map<String, Double> cpuFrequencies = getCpuFrequencies();
-            cpuFrequencies.forEach((core, freq) ->
-                    System.out.printf("CPU核心 %s 频率: %.2f GHz\n", core, freq)
-            );
-
-            // 3. 获取所有GPU信息
-            List<Map<String, String>> gpuInfoList = getMultiGpuInfo();
-            for (int i = 0; i < gpuInfoList.size(); i++) {
-                System.out.println("\nGPU #" + i + " 信息:");
-                gpuInfoList.get(i).forEach((key, value) ->
-                        System.out.println("  " + key + ": " + value)
-                );
-            }
-
-            // 4. 获取网络速率
-            String interfaceName = "eth0"; // 替换为你的网卡名称
-            NetworkStats stats1 = getNetworkStats(interfaceName);
-            TimeUnit.SECONDS.sleep(1);
-            NetworkStats stats2 = getNetworkStats(interfaceName);
-
-            long rxRate = (stats2.rxBytes - stats1.rxBytes) * 8; // 比特/秒
-            long txRate = (stats2.txBytes - stats1.txBytes) * 8; // 比特/秒
-
-            System.out.printf("\n网络速率 (%s):\n  下行: %s\n  上行: %s\n",
-                    interfaceName,
-                    formatRate(rxRate),
-                    formatRate(txRate)
-            );
-
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return null;
-    }
-
-    // 获取多CPU插槽的温度
-    private static Map<Integer, Double> getCpuTemperatures() throws Exception {
-        Map<Integer, Double> temps = new HashMap<>();
-
-        // 方法1: 通过hwmon获取物理CPU温度
-        File hwmonDir = new File("/sys/class/hwmon/");
-        for (File hwmon : hwmonDir.listFiles()) {
-            try {
-                // 检测是否为CPU温度传感器
-                File nameFile = new File(hwmon, "name");
-                if (!nameFile.exists()) continue;
-
-                try (BufferedReader nameReader = new BufferedReader(new FileReader(nameFile))) {
-                    String name = nameReader.readLine();
-                    if (name == null || !(name.contains("coretemp") || name.contains("k10temp"))) continue;
-
-                    // 查找所有物理CPU的温度
-                    for (int i = 1; i <= 16; i++) {
-                        File tempFile = new File(hwmon, "temp" + i + "_input");
-                        File labelFile = new File(hwmon, "temp" + i + "_label");
-
-                        if (tempFile.exists()) {
-                            // 读取温度标签确定是物理CPU
-                            String label = "Unknown";
-                            if (labelFile.exists()) {
-                                try (BufferedReader labelReader = new BufferedReader(new FileReader(labelFile))) {
-                                    label = labelReader.readLine();
-                                }
-                            }
-
-                            // 只获取物理CPU温度(Package或Tdie)
-                            if (label.contains("Package") || label.contains("Tdie")) {
-                                try (BufferedReader tempReader = new BufferedReader(new FileReader(tempFile))) {
-                                    double temp = Double.parseDouble(tempReader.readLine().trim()) / 1000.0;
-                                    // 提取CPU插槽ID
-                                    int socketId = 0;
-                                    Pattern p = Pattern.compile("(\\d+)");
-                                    Matcher m = p.matcher(label);
-                                    if (m.find()) socketId = Integer.parseInt(m.group());
-
-                                    temps.put(socketId, temp);
-                                }
-                            }
-                        }
-                    }
-                }
-            } catch (Exception e) {
-                // 继续下一个hwmon
-            }
-        }
-
-        // 方法2: 回退到thermal_zone方法
-        if (temps.isEmpty()) {
-            File thermalDir = new File("/sys/class/thermal/");
-            for (File zone : thermalDir.listFiles()) {
-                if (zone.getName().startsWith("thermal_zone")) {
-                    try {
-                        // 检查类型
-                        File typeFile = new File(zone, "type");
-                        if (!typeFile.exists()) continue;
-
-                        try (BufferedReader typeReader = new BufferedReader(new FileReader(typeFile))) {
-                            String type = typeReader.readLine();
-                            if (type == null || !type.contains("x86_pkg_temp")) continue;
-
-                            // 读取温度
-                            File tempFile = new File(zone, "temp");
-                            if (tempFile.exists()) {
-                                try (BufferedReader tempReader = new BufferedReader(new FileReader(tempFile))) {
-                                    double temp = Double.parseDouble(tempReader.readLine().trim()) / 1000.0;
-                                    // 尝试从目录名获取zone ID
-                                    String zoneId = zone.getName().replace("thermal_zone", "");
-                                    temps.put(Integer.parseInt(zoneId), temp);
-                                }
-                            }
-                        }
-                    } catch (Exception e) {
-                        // 忽略错误
-                    }
-                }
-            }
-        }
-
-        if (temps.isEmpty()) throw new Exception("无法检测到CPU温度传感器");
-        return temps;
-    }
-
-    // 获取所有CPU核心频率
-    private static Map<String, Double> getCpuFrequencies() throws Exception {
-        Map<String, Double> frequencies = new HashMap<>();
-        int socketCount = 0;
-        int coreCount = 0;
-
-        // 检测CPU核心
-        File cpuDir = new File("/sys/devices/system/cpu/");
-        for (File file : cpuDir.listFiles()) {
-            if (file.getName().matches("cpu\\d+")) {
-                String coreId = file.getName().replace("cpu", "");
-
-                // 读取实时频率
-                String freqPath = "/sys/devices/system/cpu/cpu" + coreId + "/cpufreq/scaling_cur_freq";
-                File freqFile = new File(freqPath);
-
-                if (freqFile.exists()) {
-                    try (BufferedReader br = new BufferedReader(new FileReader(freqFile))) {
-                        String line = br.readLine();
-                        if (line != null) {
-                            double freq = Double.parseDouble(line.trim()) / 1_000_000.0;
-                            frequencies.put("Socket0-Core" + coreId, freq);
-                        }
-                    }
-                }
-                coreCount++;
-            }
-        }
-
-        // 如果通过sysfs获取失败,尝试从/proc/cpuinfo获取
-        if (frequencies.isEmpty()) {
-            try (BufferedReader br = new BufferedReader(new FileReader("/proc/cpuinfo"))) {
-                String line;
-                int processor = -1;
-                while ((line = br.readLine()) != null) {
-                    if (line.startsWith("processor")) {
-                        String[] parts = line.split(":");
-                        processor = Integer.parseInt(parts[1].trim());
-                    } else if (line.startsWith("cpu MHz")) {
-                        String[] parts = line.split(":");
-                        double freq = Double.parseDouble(parts[1].trim()) / 1000.0;
-                        frequencies.put("Core" + processor, freq);
-                    }
-                }
-            }
-        }
-
-        return frequencies;
-    }
-
-    // 获取多GPU信息
-    private static List<Map<String, String>> getMultiGpuInfo() {
-        List<Map<String, String>> gpuList = new ArrayList<>();
-
-        // 使用nvidia-smi获取多GPU信息
-        try {
-            Process process = Runtime.getRuntime().exec(
-                    "nvidia-smi --query-gpu=index,name,temperature.gpu,utilization.gpu,memory.used,memory.total,power.draw,power.limit,clocks.current.graphics,clocks.current.memory --format=csv,noheader,nounits"
-            );
-
-            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
-            String line;
-            int gpuIndex = 0;
-
-            while ((line = reader.readLine()) != null) {
-                String[] parts = line.split(", ");
-                if (parts.length >= 10) {
-                    Map<String, String> gpuInfo = new HashMap<>();
-                    gpuInfo.put("索引", parts[0].trim());
-                    gpuInfo.put("名称", parts[1].trim());
-                    gpuInfo.put("温度", parts[2].trim() + "°C");
-                    gpuInfo.put("使用率", parts[3].trim() + "%");
-                    gpuInfo.put("显存使用", parts[4].trim() + " MB");
-                    gpuInfo.put("总显存", parts[5].trim() + " MB");
-                    gpuInfo.put("功耗", parts[6].trim() + " W");
-                    gpuInfo.put("功耗限制", parts[7].trim() + " W");
-                    gpuInfo.put("图形时钟", parts[8].trim() + " MHz");
-                    gpuInfo.put("显存时钟", parts[9].trim() + " MHz");
-
-                    gpuList.add(gpuInfo);
-                    gpuIndex++;
-                }
-            }
-
-            // 检查是否有GPU被检测到
-            if (gpuIndex > 0) return gpuList;
-        } catch (Exception e) {
-            System.err.println("nvidia-smi执行错误: " + e.getMessage());
-        }
-
-        // 备用方法:尝试从sysfs读取AMD/集成GPU信息
-        try {
-            File hwmonDir = new File("/sys/class/hwmon/");
-            for (File hwmon : hwmonDir.listFiles()) {
-                try {
-                    // 检测是否为GPU
-                    File nameFile = new File(hwmon, "name");
-                    if (!nameFile.exists()) continue;
-
-                    try (BufferedReader nameReader = new BufferedReader(new FileReader(nameFile))) {
-                        String name = nameReader.readLine();
-                        if (name == null || !(name.contains("amdgpu") || name.contains("radeon"))) continue;
-
-                        Map<String, String> gpuInfo = new HashMap<>();
-                        gpuInfo.put("类型", name);
-
-                        // 读取温度
-                        for (int i = 1; i <= 5; i++) {
-                            File tempFile = new File(hwmon, "temp" + i + "_input");
-                            if (tempFile.exists()) {
-                                try (BufferedReader tempReader = new BufferedReader(new FileReader(tempFile))) {
-                                    double temp = Double.parseDouble(tempReader.readLine().trim()) / 1000.0;
-                                    gpuInfo.put("温度", String.format("%.1f°C", temp));
-                                    break;
-                                }
-                            }
-                        }
-
-                        // 读取功耗(如果可用)
-                        File powerFile = new File(hwmon, "power1_average");
-                        if (powerFile.exists()) {
-                            try (BufferedReader powerReader = new BufferedReader(new FileReader(powerFile))) {
-                                double power = Double.parseDouble(powerReader.readLine().trim()) / 1_000_000.0;
-                                gpuInfo.put("功耗", String.format("%.1f W", power));
-                            }
-                        }
-
-                        gpuList.add(gpuInfo);
-                    }
-                } catch (Exception e) {
-                    // 继续下一个hwmon
-                }
-            }
-        } catch (Exception e) {
-            System.err.println("sysfs读取错误: " + e.getMessage());
-        }
-
-        // 如果没有检测到GPU,添加一个错误信息
-        if (gpuList.isEmpty()) {
-            Map<String, String> errorInfo = new HashMap<>();
-            errorInfo.put("错误", "未检测到GPU或需要安装驱动");
-            gpuList.add(errorInfo);
-        }
-
-        return gpuList;
-    }
-
-    // 网络统计数据结构
-    private static class NetworkStats {
-        long rxBytes;
-        long txBytes;
-    }
-
-    // 获取网络统计信息
-    private static NetworkStats getNetworkStats(String interfaceName) throws Exception {
-        NetworkStats stats = new NetworkStats();
-        File file = new File("/proc/net/dev");
-
-        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
-            String line;
-            while ((line = br.readLine()) != null) {
-                line = line.trim();
-                if (line.startsWith(interfaceName + ":")) {
-                    String[] parts = line.split("\\s+");
-                    stats.rxBytes = Long.parseLong(parts[1]);
-                    stats.txBytes = Long.parseLong(parts[9]);
-                    break;
-                }
-            }
-        }
-        return stats;
-    }
-
-    // 格式化网络速率
-    private static String formatRate(long rateBitsPerSec) {
-        if (rateBitsPerSec < 1000) {
-            return rateBitsPerSec + " bps";
-        } else if (rateBitsPerSec < 1_000_000) {
-            return String.format("%.2f Kbps", rateBitsPerSec / 1000.0);
-        } else if (rateBitsPerSec < 1_000_000_000) {
-            return String.format("%.2f Mbps", rateBitsPerSec / 1_000_000.0);
-        } else {
-            return String.format("%.2f Gbps", rateBitsPerSec / 1_000_000_000.0);
-        }
-    }
-}

+ 701 - 77
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java

@@ -5,7 +5,6 @@ import com.ruoyi.common.utils.ip.IpUtils;
 import com.ruoyi.framework.web.domain.server.*;
 import oshi.SystemInfo;
 import oshi.hardware.CentralProcessor;
-import oshi.hardware.CentralProcessor.TickType;
 import oshi.hardware.GlobalMemory;
 import oshi.hardware.HardwareAbstractionLayer;
 import oshi.software.os.FileSystem;
@@ -13,26 +12,37 @@ import oshi.software.os.OSFileStore;
 import oshi.software.os.OperatingSystem;
 import oshi.util.Util;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStreamReader;
 import java.net.UnknownHostException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Properties;
-
-import static com.ruoyi.framework.web.domain.AdvancedSystemMonitor.advancedSystem;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * 服务器相关信息
- * 
+ *
  * @author ruoyi
  */
-public class Server
-{
+public class Server {
     private static final int OSHI_WAIT_SECOND = 1000;
-    
+
     /**
      * CPU相关信息
      */
     private Cpu cpu = new Cpu();
+    private List<Cpu> cpuList = new ArrayList<>();
+
+    /**
+     * GPU
+     */
+    private List<Gpu> gpuList = new ArrayList<>();
+    /**
+     * 网络
+     */
+    private List<Network> netWorkList = new ArrayList<>();
 
     /**
      * 內存相关信息
@@ -54,58 +64,71 @@ public class Server
      */
     private List<SysFile> sysFiles = new LinkedList<SysFile>();
 
-    public Cpu getCpu()
-    {
+    public List<Network> getNetWorkList() {
+        return netWorkList;
+    }
+
+    public void setNetWorkList(List<Network> netWorkList) {
+        this.netWorkList = netWorkList;
+    }
+
+    public List<Gpu> getGpuList() {
+        return gpuList;
+    }
+
+    public void setGpuList(List<Gpu> gpuList) {
+        this.gpuList = gpuList;
+    }
+
+    public List<Cpu> getCpuList() {
+        return cpuList;
+    }
+
+    public void setCpuList(List<Cpu> cpuList) {
+        this.cpuList = cpuList;
+    }
+
+    public Cpu getCpu() {
         return cpu;
     }
 
-    public void setCpu(Cpu cpu)
-    {
+    public void setCpu(Cpu cpu) {
         this.cpu = cpu;
     }
 
-    public Mem getMem()
-    {
+    public Mem getMem() {
         return mem;
     }
 
-    public void setMem(Mem mem)
-    {
+    public void setMem(Mem mem) {
         this.mem = mem;
     }
 
-    public Jvm getJvm()
-    {
+    public Jvm getJvm() {
         return jvm;
     }
 
-    public void setJvm(Jvm jvm)
-    {
+    public void setJvm(Jvm jvm) {
         this.jvm = jvm;
     }
 
-    public Sys getSys()
-    {
+    public Sys getSys() {
         return sys;
     }
 
-    public void setSys(Sys sys)
-    {
+    public void setSys(Sys sys) {
         this.sys = sys;
     }
 
-    public List<SysFile> getSysFiles()
-    {
+    public List<SysFile> getSysFiles() {
         return sysFiles;
     }
 
-    public void setSysFiles(List<SysFile> sysFiles)
-    {
+    public void setSysFiles(List<SysFile> sysFiles) {
         this.sysFiles = sysFiles;
     }
 
-    public void copyTo() throws Exception
-    {
+    public void copyTo() throws Exception {
         SystemInfo si = new SystemInfo();
         HardwareAbstractionLayer hal = si.getHardware();
 
@@ -119,40 +142,653 @@ public class Server
 
         setSysFiles(si.getOperatingSystem());
 
-        advancedSystem();
+        setGpuInfo();
+
+        List<String> interfaceNameList = new ArrayList<>();
+        interfaceNameList.add("eno1np0");// 替换为你的网卡名称
+        interfaceNameList.add("eno2np1");// 替换为你的网卡名称
+        for (String interfaceName : interfaceNameList) {
+            Network netWork = new Network();
+            NetworkStats stats1 = getNetworkStats(interfaceName);
+            if (stats1 != null) {
+                Util.sleep(OSHI_WAIT_SECOND);
+                NetworkStats stats2 = getNetworkStats(interfaceName);
+                long rxRate = (stats2.rxBytes - stats1.rxBytes) * 8; // 比特/秒
+                long txRate = (stats2.txBytes - stats1.txBytes) * 8; // 比特/秒
+                netWork.setNetworkName(interfaceName);
+                netWork.setNetworkUp(txRate);
+                netWork.setNetworkDown(rxRate);
+                netWorkList.add(netWork);
+                System.out.printf("\n网络速率 (%s):\n  下行: %s\n  上行: %s\n",
+                        interfaceName,
+                        formatRate(rxRate),
+                        formatRate(txRate)
+                );
+            }
+        }
+    }
+
+    // 网络统计数据结构
+    private static class NetworkStats {
+        long rxBytes;
+        long txBytes;
+    }
+
+    // 获取网络统计信息
+    private static NetworkStats getNetworkStats(String interfaceName) throws Exception {
+        File file = new File("/proc/net/dev");
+        if (file == null || !file.exists()) {
+            return null;
+        }
+        NetworkStats stats = new NetworkStats();
+        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                line = line.trim();
+                if (line.startsWith(interfaceName + ":")) {
+                    String[] parts = line.split("\\s+");
+                    stats.rxBytes = Long.parseLong(parts[1]);
+                    stats.txBytes = Long.parseLong(parts[9]);
+                    break;
+                }
+            }
+        }
+        return stats;
+    }
+
+    // 格式化网络速率
+    private static String formatRate(long rateBitsPerSec) {
+        if (rateBitsPerSec < 1000) {
+            return rateBitsPerSec + " bps";
+        } else if (rateBitsPerSec < 1_000_000) {
+            return String.format("%.2f Kbps", rateBitsPerSec / 1000.0);
+        } else if (rateBitsPerSec < 1_000_000_000) {
+            return String.format("%.2f Mbps", rateBitsPerSec / 1_000_000.0);
+        } else {
+            return String.format("%.2f Gbps", rateBitsPerSec / 1_000_000_000.0);
+        }
+    }
+
+    private void setGpuInfo() {
+        // 3. 获取所有GPU信息
+        List<Map<String, String>> gpuInfoList = getMultiGpuInfo();
+        for (int i = 0; i < gpuInfoList.size(); i++) {
+            Gpu gpu = new Gpu();
+            System.out.println("\nGPU #" + i + " 信息:");
+            for (Map<String, String> stringStringMap : gpuInfoList) {
+                gpu.setName(stringStringMap.get("名称"));
+                gpu.setGpuMemory(stringStringMap.get("总显存"));
+                gpu.setUseMemory(stringStringMap.get("显存使用"));
+                gpu.setUsed(stringStringMap.get("使用率"));
+                gpu.setTemperature(stringStringMap.get("温度"));
+                gpu.setPower(stringStringMap.get("功耗"));
+                gpuList.add(gpu);
+            }
+            gpuInfoList.get(i).forEach((key, value) ->
+                    System.out.println("  " + key + ": " + value)
+            );
+        }
+    }
+
+    // 获取多GPU信息
+    private static List<Map<String, String>> getMultiGpuInfo() {
+        List<Map<String, String>> gpuList = new ArrayList<>();
+
+        // 使用nvidia-smi获取多GPU信息
+        try {
+            Process process = Runtime.getRuntime().exec(
+                    "nvidia-smi --query-gpu=index,name,temperature.gpu,utilization.gpu,memory.used,memory.total,power.draw,power.limit,clocks.current.graphics,clocks.current.memory --format=csv,noheader,nounits"
+            );
+
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            int gpuIndex = 0;
+
+            while ((line = reader.readLine()) != null) {
+                String[] parts = line.split(", ");
+                if (parts.length >= 10) {
+                    Map<String, String> gpuInfo = new HashMap<>();
+                    gpuInfo.put("索引", parts[0].trim());
+                    gpuInfo.put("名称", parts[1].trim());
+                    gpuInfo.put("温度", parts[2].trim() + "°C");
+                    gpuInfo.put("使用率", parts[3].trim() + "%");
+                    gpuInfo.put("显存使用", parts[4].trim() + " MB");
+                    gpuInfo.put("总显存", parts[5].trim() + " MB");
+                    gpuInfo.put("功耗", parts[6].trim() + " W");
+                    gpuInfo.put("功耗限制", parts[7].trim() + " W");
+                    gpuInfo.put("图形时钟", parts[8].trim() + " MHz");
+                    gpuInfo.put("显存时钟", parts[9].trim() + " MHz");
+                    gpuList.add(gpuInfo);
+                    gpuIndex++;
+                }
+            }
+
+            // 检查是否有GPU被检测到
+            if (gpuIndex > 0) return gpuList;
+        } catch (Exception e) {
+            System.err.println("nvidia-smi执行错误: " + e.getMessage());
+        }
+
+        // 备用方法:尝试从sysfs读取AMD/集成GPU信息
+        try {
+            File hwmonDir = new File("/sys/class/hwmon/");
+            for (File hwmon : hwmonDir.listFiles()) {
+                try {
+                    // 检测是否为GPU
+                    File nameFile = new File(hwmon, "name");
+                    if (!nameFile.exists()) continue;
+
+                    try (BufferedReader nameReader = new BufferedReader(new FileReader(nameFile))) {
+                        String name = nameReader.readLine();
+                        if (name == null || !(name.contains("amdgpu") || name.contains("radeon"))) continue;
+
+                        Map<String, String> gpuInfo = new HashMap<>();
+                        gpuInfo.put("类型", name);
+
+                        // 读取温度
+                        for (int i = 1; i <= 5; i++) {
+                            File tempFile = new File(hwmon, "temp" + i + "_input");
+                            if (tempFile.exists()) {
+                                try (BufferedReader tempReader = new BufferedReader(new FileReader(tempFile))) {
+                                    double temp = Double.parseDouble(tempReader.readLine().trim()) / 1000.0;
+                                    gpuInfo.put("温度", String.format("%.1f°C", temp));
+                                    break;
+                                }
+                            }
+                        }
+
+                        // 读取功耗(如果可用)
+                        File powerFile = new File(hwmon, "power1_average");
+                        if (powerFile.exists()) {
+                            try (BufferedReader powerReader = new BufferedReader(new FileReader(powerFile))) {
+                                double power = Double.parseDouble(powerReader.readLine().trim()) / 1_000_000.0;
+                                gpuInfo.put("功耗", String.format("%.1f W", power));
+                            }
+                        }
+
+                        gpuList.add(gpuInfo);
+                    }
+                } catch (Exception e) {
+                    // 继续下一个hwmon
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("sysfs读取错误: " + e.getMessage());
+        }
+
+        // 如果没有检测到GPU,添加一个错误信息
+        if (gpuList.isEmpty()) {
+            Map<String, String> errorInfo = new HashMap<>();
+            errorInfo.put("错误", "未检测到GPU或需要安装驱动");
+            gpuList.add(errorInfo);
+        }
+
+        return gpuList;
     }
 
     /**
      * 设置CPU信息
      */
-    private void setCpuInfo(CentralProcessor processor)
-    {
-        // CPU信息
-        long[] prevTicks = processor.getSystemCpuLoadTicks();
+    private void setCpuInfo(CentralProcessor processor) {
+        // 1. 获取物理CPU数量
+        int physicalPackageCount = processor.getPhysicalPackageCount();
+        // 2分组方法:使用物理处理器编号分组
+        Map<Integer, List<CentralProcessor.LogicalProcessor>> cpuGroups = new HashMap<>();
+        for (CentralProcessor.LogicalProcessor lp : processor.getLogicalProcessors()) {
+            int packageId = lp.getPhysicalProcessorNumber() % physicalPackageCount;
+            cpuGroups.computeIfAbsent(packageId, k -> new ArrayList<>()).add(lp);
+        }
+
+        // 3. 获取初始CPU滴答计数(按逻辑处理器)
+        long[][] initialTicks = processor.getProcessorCpuLoadTicks();
+
+        // 4. 等待1秒获取新数据
         Util.sleep(OSHI_WAIT_SECOND);
-        long[] ticks = processor.getSystemCpuLoadTicks();
-        long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
-        long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
-        long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
-        long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
-        long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
-        long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
-        long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
-        long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
-        long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
-        cpu.setCpuNum(processor.getLogicalProcessorCount());
-        cpu.setTotal(totalCpu);
-        cpu.setSys(cSys);
-        cpu.setUsed(user);
-        cpu.setWait(iowait);
-        cpu.setFree(idle);
+
+        // 5. 获取新的CPU滴答计数
+        long[][] newTicks = processor.getProcessorCpuLoadTicks();
+
+        // 6. 计算并显示每个物理CPU的数据
+        for (int pkgId = 0; pkgId < physicalPackageCount; pkgId++) {
+            Cpu cpuOne = new Cpu();
+            if (!cpuGroups.containsKey(pkgId)) continue;
+            System.out.println("\n===== 物理CPU " + (pkgId + 1) + " 数据 =====");
+            List<CentralProcessor.LogicalProcessor> group = cpuGroups.get(pkgId);
+            System.out.println("包含的逻辑处理器数量: " + cpuGroups.get(pkgId).size());
+            cpuOne.setCpuNum(cpuGroups.get(pkgId).size());
+            // 计算该物理CPU的总负载百分比
+            double totalLoad = calculatePackageLoad(initialTicks, newTicks, group);
+            System.out.printf("总负载: %.2f%%\n", totalLoad * 100);
+            cpuOne.setTotal(Arith.round(totalLoad * 100, 2));
+            // 计算并显示各个详细负载的百分比
+            printPackageLoadDetails(initialTicks, newTicks, group, cpuOne);
+            cpuList.add(cpuOne);
+        }
+
+        // 1. 获取所有CPU插槽的温度
+        Map<Integer, Double> cpuTemps = getCpuTemperatures();
+        if (cpuTemps != null) {
+            for (Integer socket : cpuTemps.keySet()) {
+                Double temp = cpuTemps.get(socket);
+                cpuList.get(socket).setTemperature(temp);
+                System.out.printf("CPU插槽 %d 温度: %.1f°C\n", socket, temp);
+            }
+        }
+        // 2. 获取双CPU的整体速度
+        Map<Integer, Double> cpuSpeeds = getCpuSpeeds();
+        if (!cpuSpeeds.isEmpty()) {
+            for (Integer socket : cpuSpeeds.keySet()) {
+                cpuList.get(socket).setSpeed(cpuSpeeds.get(socket));
+                System.out.printf("CPU插槽 %d 整体速度: %.2f GHz\n", socket, cpuSpeeds.get(socket));
+            }
+        }
+    }
+
+    // 获取双CPU整体速度
+    private static Map<Integer, Double> getCpuSpeeds() {
+        Map<Integer, Double> speeds = new HashMap<>();
+
+        // 方法1: 从/proc/cpuinfo获取每个物理CPU的平均速度
+        try (BufferedReader br = new BufferedReader(new FileReader("/proc/cpuinfo"))) {
+            String line;
+            int currentSocket = -1;
+            Map<Integer, Double> socketTotalSpeed = new HashMap<>();
+            Map<Integer, Integer> socketCoreCount = new HashMap<>();
+
+            while ((line = br.readLine()) != null) {
+                if (line.startsWith("physical id")) {
+                    // 提取物理CPU ID
+                    String[] parts = line.split(":");
+                    currentSocket = Integer.parseInt(parts[1].trim());
+
+                    // 初始化计数器
+                    if (!socketTotalSpeed.containsKey(currentSocket)) {
+                        socketTotalSpeed.put(currentSocket, 0.0);
+                        socketCoreCount.put(currentSocket, 0);
+                    }
+                } else if (line.startsWith("cpu MHz") && currentSocket >= 0) {
+                    // 提取核心速度
+                    String[] parts = line.split(":");
+                    double coreSpeed = Double.parseDouble(parts[1].trim()) / 1000.0; // MHz转GHz
+
+                    // 累加到所属物理CPU
+                    socketTotalSpeed.put(currentSocket, socketTotalSpeed.get(currentSocket) + coreSpeed);
+                    socketCoreCount.put(currentSocket, socketCoreCount.get(currentSocket) + 1);
+                }
+            }
+
+            // 计算每个物理CPU的平均速度
+            for (Integer socket : socketTotalSpeed.keySet()) {
+                double totalSpeed = socketTotalSpeed.get(socket);
+                int coreCount = socketCoreCount.get(socket);
+                speeds.put(socket, totalSpeed / coreCount);
+            }
+
+            if (!speeds.isEmpty()) return speeds;
+        } catch (Exception e) {
+            System.err.println("/proc/cpuinfo读取错误: " + e.getMessage());
+        }
+
+        // 方法2: 使用lscpu命令获取物理CPU速度
+        try {
+            Process process = Runtime.getRuntime().exec("lscpu -p=socket,core,CPU,MHz");
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            Map<Integer, Double> socketTotalSpeed = new HashMap<>();
+            Map<Integer, Integer> socketCoreCount = new HashMap<>();
+
+            while ((line = reader.readLine()) != null) {
+                // 跳过注释行
+                if (line.startsWith("#")) continue;
+
+                String[] parts = line.split(",");
+                if (parts.length >= 4) {
+                    try {
+                        int socket = Integer.parseInt(parts[0]);
+                        double speed = Double.parseDouble(parts[3]) / 1000.0; // MHz转GHz
+
+                        socketTotalSpeed.putIfAbsent(socket, 0.0);
+                        socketCoreCount.putIfAbsent(socket, 0);
+
+                        socketTotalSpeed.put(socket, socketTotalSpeed.get(socket) + speed);
+                        socketCoreCount.put(socket, socketCoreCount.get(socket) + 1);
+                    } catch (NumberFormatException e) {
+                        // 忽略格式错误行
+                    }
+                }
+            }
+
+            // 计算每个物理CPU的平均速度
+            for (Integer socket : socketTotalSpeed.keySet()) {
+                double totalSpeed = socketTotalSpeed.get(socket);
+                int coreCount = socketCoreCount.get(socket);
+                speeds.put(socket, totalSpeed / coreCount);
+            }
+
+            if (!speeds.isEmpty()) return speeds;
+        } catch (Exception e) {
+            System.err.println("lscpu命令执行错误: " + e.getMessage());
+        }
+
+        // 方法3: 使用sysfs接口(回退方案)
+        try {
+            File cpuDir = new File("/sys/devices/system/cpu/");
+            Map<Integer, Double> socketTotalSpeed = new HashMap<>();
+            Map<Integer, Integer> socketCoreCount = new HashMap<>();
+
+            for (File file : cpuDir.listFiles()) {
+                if (file.getName().matches("cpu\\d+")) {
+                    String coreId = file.getName().replace("cpu", "");
+
+                    // 获取物理CPU ID
+                    File topologyDir = new File(file, "topology");
+                    File physicalPackageIdFile = new File(topologyDir, "physical_package_id");
+                    int socket = 0;
+
+                    if (physicalPackageIdFile.exists()) {
+                        try (BufferedReader br = new BufferedReader(new FileReader(physicalPackageIdFile))) {
+                            socket = Integer.parseInt(br.readLine().trim());
+                        }
+                    }
+
+                    // 读取核心速度
+                    String freqPath = "/sys/devices/system/cpu/cpu" + coreId + "/cpufreq/scaling_cur_freq";
+                    File freqFile = new File(freqPath);
+
+                    if (freqFile.exists()) {
+                        try (BufferedReader br = new BufferedReader(new FileReader(freqFile))) {
+                            String line = br.readLine();
+                            if (line != null) {
+                                double freq = Double.parseDouble(line.trim()) / 1_000_000.0; // 转换为GHz
+
+                                socketTotalSpeed.putIfAbsent(socket, 0.0);
+                                socketCoreCount.putIfAbsent(socket, 0);
+
+                                socketTotalSpeed.put(socket, socketTotalSpeed.get(socket) + freq);
+                                socketCoreCount.put(socket, socketCoreCount.get(socket) + 1);
+                            }
+                        }
+                    }
+                }
+            }
+
+            // 计算每个物理CPU的平均速度
+            for (Integer socket : socketTotalSpeed.keySet()) {
+                double totalSpeed = socketTotalSpeed.get(socket);
+                int coreCount = socketCoreCount.get(socket);
+                speeds.put(socket, totalSpeed / coreCount);
+            }
+
+            if (!speeds.isEmpty()) return speeds;
+        } catch (Exception e) {
+            System.err.println("sysfs接口访问错误: " + e.getMessage());
+        }
+        System.out.println("无法检测到CPU速度");
+        return speeds;
+    }
+    // 获取双CPU温度
+
+    private static Map<Integer, Double> getCpuTemperatures() {
+        Map<Integer, Double> temps = new HashMap<>();
+        int cpuCount = 0;
+
+        // 方法1: 使用sensors命令并解析文本输出
+        try {
+            Process process = Runtime.getRuntime().exec("sensors");
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            boolean inCpuSection = false;
+            boolean foundTctl = false;
+
+            while ((line = reader.readLine()) != null) {
+                line = line.trim();
+
+                // 1. 检测适配器部分开始
+                if (line.endsWith(":") || (line.contains("Adapter:") && !line.contains(":"))) {
+                    // 检查是否是CPU适配器
+                    inCpuSection = isCpuAdapterSection(line);
+                    foundTctl = false;
+                    continue;
+                }
+
+                // 2. 在CPU部分处理温度行
+                if (inCpuSection) {
+                    // 3. 检测CPU温度行 (Tctl, Tdie, Package)
+                    if (isCpuTemperatureLine(line)) {
+                        // 提取温度值
+                        Double temp = extractTemperatureValue(line);
+                        if (temp != null) {
+                            temps.put(cpuCount, temp);
+                            System.out.println("检测到CPU " + cpuCount + " 温度: " + temp + "°C");
+                            foundTctl = true;
+                        }
+                    }
+                    // 4. 当检测到非温度行且已有温度值时,结束当前CPU部分
+                    else if (foundTctl && !line.isEmpty()) {
+                        inCpuSection = false;
+                        cpuCount++;
+                    }
+                }
+            }
+
+            // 处理最后一个CPU部分
+            if (foundTctl) {
+                cpuCount++;
+            }
+
+            if (!temps.isEmpty()) return temps;
+        } catch (Exception e) {
+            System.err.println("sensors命令执行错误: " + e.getMessage());
+        }
+
+        // 方法2: 备用方案 - 直接搜索所有温度行
+        if (temps.isEmpty()) {
+            try {
+                Process process = Runtime.getRuntime().exec("sensors");
+                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                String line;
+                int cpuIndex = 0;
+
+                while ((line = reader.readLine()) != null) {
+                    if (isCpuTemperatureLine(line)) {
+                        Double temp = extractTemperatureValue(line);
+                        if (temp != null) {
+                            temps.put(cpuIndex++, temp);
+                            System.out.println("通过备用方法检测到CPU " + (cpuIndex-1) + " 温度: " + temp + "°C");
+                        }
+                    }
+                }
+
+                if (!temps.isEmpty()) return temps;
+            } catch (Exception e) {
+                System.err.println("备用sensors解析错误: " + e.getMessage());
+            }
+        }
+
+        // 方法3: 使用hwmon接口(不依赖固定名称)
+        try {
+            File hwmonDir = new File("/sys/class/hwmon/");
+            File[] hwmonDirs = hwmonDir.listFiles();
+            if (hwmonDirs == null) return temps;
+
+            for (File hwmon : hwmonDirs) {
+                try {
+                    // 读取设备名(但不依赖特定名称)
+                    File nameFile = new File(hwmon, "name");
+                    if (!nameFile.exists()) continue;
+
+                    // 查找标签包含CPU特征的文件
+                    File[] labelFiles = hwmon.listFiles((dir, filename) ->
+                            filename.startsWith("temp") && filename.endsWith("_label"));
+
+                    for (File labelFile : labelFiles) {
+                        String baseName = labelFile.getName().replace("_label", "");
+                        File tempFile = new File(hwmon, baseName + "_input");
+
+                        if (!tempFile.exists()) continue;
+
+                        // 读取温度标签
+                        String label;
+                        try (BufferedReader labelReader = new BufferedReader(new FileReader(labelFile))) {
+                            label = labelReader.readLine();
+                        }
+
+                        // 检测物理CPU温度特征
+                        if (isCpuTemperatureLabel(label)) {
+                            // 读取温度值
+                            double temp;
+                            try (BufferedReader tempReader = new BufferedReader(new FileReader(tempFile))) {
+                                temp = Double.parseDouble(tempReader.readLine().trim()) / 1000.0;
+                            }
+
+                            // 添加到结果
+                            temps.put(cpuCount, temp);
+                            System.out.println("通过hwmon检测到CPU " + cpuCount + " 温度: " + temp + "°C");
+                            cpuCount++;
+                        }
+                    }
+                } catch (Exception e) {
+                    System.err.println("hwmon处理错误: " + hwmon.getName() + " - " + e.getMessage());
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("hwmon接口访问错误: " + e.getMessage());
+        }
+
+        if (temps.isEmpty()) {
+            System.out.println("无法检测到CPU温度传感器");
+        }
+        return temps;
+    }
+
+    // 判断是否CPU适配器部分
+    private static boolean isCpuAdapterSection(String line) {
+        // 空行或包含非CPU设备特征
+        if (line.isEmpty() || line.contains("nvme") || line.contains("bnxt") ||
+                line.contains("Composite") || line.contains("Sensor")) {
+            return false;
+        }
+
+        // 包含CPU特征
+        return line.contains("temp") || line.contains("Adapter") ||
+                line.contains("k10") || line.contains("core");
+    }
+
+    // 判断是否CPU温度行
+    private static boolean isCpuTemperatureLine(String line) {
+        if (line.isEmpty()) return false;
+
+        // 匹配CPU温度特征
+        return line.startsWith("Tctl:") ||
+                line.startsWith("Tdie:") ||
+                line.startsWith("Package id") ||
+                line.startsWith("CPU Package") ||
+                line.contains("Core") ||
+                (line.contains("°C") && (line.contains("Package") || line.contains("Core")));
+    }
+
+    // 提取温度值
+    private static Double extractTemperatureValue(String line) {
+        // 匹配温度模式:+34.6°C 或 34.6
+        Pattern pattern = Pattern.compile("([+-]?[\\d\\.]+)\\s*°?C");
+        Matcher matcher = pattern.matcher(line);
+        if (matcher.find()) {
+            try {
+                return Double.parseDouble(matcher.group(1));
+            } catch (NumberFormatException e) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    // 判断是否CPU温度标签
+    private static boolean isCpuTemperatureLabel(String label) {
+        if (label == null) return false;
+
+        // 匹配CPU温度特征
+        return label.contains("Tctl") ||
+                label.contains("Tdie") ||
+                label.contains("Package") ||
+                label.contains("Core") ||
+                label.contains("CPU");
+    }
+
+    private static void printPackageLoadDetails(long[][] initialTicks, long[][] newTicks,List<CentralProcessor.LogicalProcessor> group,Cpu cpuOne) {
+        // 初始化各类型总增量
+        long totalUser = 0, totalNice = 0, totalSystem = 0, totalIdle = 0;
+        long totalIowait = 0, totalIrq = 0, totalSoftirq = 0, totalSteal = 0;
+        long totalAll = 0;
+
+        // 计算该物理CPU组内所有逻辑处理器的增量总和
+        for (CentralProcessor.LogicalProcessor lp : group) {
+            int processorNumber = lp.getProcessorNumber();
+            long[] initial = initialTicks[processorNumber];
+            long[] current = newTicks[processorNumber];
+
+            totalUser += current[CentralProcessor.TickType.USER.getIndex()] - initial[CentralProcessor.TickType.USER.getIndex()];
+            totalNice += current[CentralProcessor.TickType.NICE.getIndex()] - initial[CentralProcessor.TickType.NICE.getIndex()];
+            totalSystem += current[CentralProcessor.TickType.SYSTEM.getIndex()] - initial[CentralProcessor.TickType.SYSTEM.getIndex()];
+            totalIdle += current[CentralProcessor.TickType.IDLE.getIndex()] - initial[CentralProcessor.TickType.IDLE.getIndex()];
+            totalIowait += current[CentralProcessor.TickType.IOWAIT.getIndex()] - initial[CentralProcessor.TickType.IOWAIT.getIndex()];
+            totalIrq += current[CentralProcessor.TickType.IRQ.getIndex()] - initial[CentralProcessor.TickType.IRQ.getIndex()];
+            totalSoftirq += current[CentralProcessor.TickType.SOFTIRQ.getIndex()] - initial[CentralProcessor.TickType.SOFTIRQ.getIndex()];
+            totalSteal += current[CentralProcessor.TickType.STEAL.getIndex()] - initial[CentralProcessor.TickType.STEAL.getIndex()];
+        }
+
+        // 计算总时间
+        totalAll = totalUser + totalNice + totalSystem + totalIdle +
+                totalIowait + totalIrq + totalSoftirq + totalSteal;
+
+        System.out.println("详细负载百分比:");
+
+        // 计算并显示每个负载类型的百分比(保留两位小数)
+        if (totalAll > 0) {
+            System.out.printf("  User:   %.2f%%\n", 100.0 * totalUser / totalAll);
+            System.out.printf("  Nice:   %.2f%%\n", 100.0 * totalNice / totalAll);
+            System.out.printf("  System: %.2f%%\n", 100.0 * totalSystem / totalAll);
+            System.out.printf("  Idle:   %.2f%%\n", 100.0 * totalIdle / totalAll);
+            System.out.printf("  IOWait: %.2f%%\n", 100.0 * totalIowait / totalAll);
+            System.out.printf("  IRQ:    %.2f%%\n", 100.0 * totalIrq / totalAll);
+            System.out.printf("  SoftIRQ:%.2f%%\n", 100.0 * totalSoftirq / totalAll);
+            System.out.printf("  Steal:  %.2f%%\n", 100.0 * totalSteal / totalAll);
+            cpuOne.setSys(Arith.round(100.0 * totalSystem / totalAll, 2));
+            cpuOne.setFree(Arith.round(100.0 * totalIdle / totalAll, 2));
+            cpuOne.setUsed(Arith.round(100.0 * totalUser / totalAll, 2));
+        } else {
+            System.out.println("  无有效负载数据");
+        }
+    }
+
+    private static double calculatePackageLoad(long[][] initialTicks, long[][] newTicks, List<CentralProcessor.LogicalProcessor> group) {
+        long totalUsed = 0;
+        long total = 0;
+
+        for (CentralProcessor.LogicalProcessor lp : group) {
+            int processorNumber = lp.getProcessorNumber();
+            long[] initial = initialTicks[processorNumber];
+            long[] current = newTicks[processorNumber];
+
+            // 计算该逻辑处理器的负载增量
+            long user = current[CentralProcessor.TickType.USER.getIndex()] - initial[CentralProcessor.TickType.USER.getIndex()];
+            long nice = current[CentralProcessor.TickType.NICE.getIndex()] - initial[CentralProcessor.TickType.NICE.getIndex()];
+            long system = current[CentralProcessor.TickType.SYSTEM.getIndex()] - initial[CentralProcessor.TickType.SYSTEM.getIndex()];
+            long idle = current[CentralProcessor.TickType.IDLE.getIndex()] - initial[CentralProcessor.TickType.IDLE.getIndex()];
+            long iowait = current[CentralProcessor.TickType.IOWAIT.getIndex()] - initial[CentralProcessor.TickType.IOWAIT.getIndex()];
+            long irq = current[CentralProcessor.TickType.IRQ.getIndex()] - initial[CentralProcessor.TickType.IRQ.getIndex()];
+            long softirq = current[CentralProcessor.TickType.SOFTIRQ.getIndex()] - initial[CentralProcessor.TickType.SOFTIRQ.getIndex()];
+            long steal = current[CentralProcessor.TickType.STEAL.getIndex()] - initial[CentralProcessor.TickType.STEAL.getIndex()];
+
+            totalUsed += user + nice + system + irq + softirq + steal;
+            total += totalUsed + idle + iowait;
+        }
+        return total > 0 ? (double) totalUsed / total : 0;
     }
 
     /**
      * 设置内存信息
      */
-    private void setMemInfo(GlobalMemory memory)
-    {
+    private void setMemInfo(GlobalMemory memory) {
         mem.setTotal(memory.getTotal());
         mem.setUsed(memory.getTotal() - memory.getAvailable());
         mem.setFree(memory.getAvailable());
@@ -161,8 +797,7 @@ public class Server
     /**
      * 设置服务器信息
      */
-    private void setSysInfo()
-    {
+    private void setSysInfo() {
         Properties props = System.getProperties();
         sys.setComputerName(IpUtils.getHostName());
         sys.setComputerIp(IpUtils.getHostIp());
@@ -174,8 +809,7 @@ public class Server
     /**
      * 设置Java虚拟机
      */
-    private void setJvmInfo() throws UnknownHostException
-    {
+    private void setJvmInfo() throws UnknownHostException {
         Properties props = System.getProperties();
         jvm.setTotal(Runtime.getRuntime().totalMemory());
         jvm.setMax(Runtime.getRuntime().maxMemory());
@@ -187,12 +821,10 @@ public class Server
     /**
      * 设置磁盘信息
      */
-    private void setSysFiles(OperatingSystem os)
-    {
+    private void setSysFiles(OperatingSystem os) {
         FileSystem fileSystem = os.getFileSystem();
         List<OSFileStore> fsArray = fileSystem.getFileStores();
-        for (OSFileStore fs : fsArray)
-        {
+        for (OSFileStore fs : fsArray) {
             long free = fs.getUsableSpace();
             long total = fs.getTotalSpace();
             long used = total - free;
@@ -210,31 +842,23 @@ public class Server
 
     /**
      * 字节转换
-     * 
+     *
      * @param size 字节大小
      * @return 转换后值
      */
-    public String convertFileSize(long size)
-    {
+    public String convertFileSize(long size) {
         long kb = 1024;
         long mb = kb * 1024;
         long gb = mb * 1024;
-        if (size >= gb)
-        {
+        if (size >= gb) {
             return String.format("%.1f GB", (float) size / gb);
-        }
-        else if (size >= mb)
-        {
+        } else if (size >= mb) {
             float f = (float) size / mb;
             return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
-        }
-        else if (size >= kb)
-        {
+        } else if (size >= kb) {
             float f = (float) size / kb;
             return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
-        }
-        else
-        {
+        } else {
             return String.format("%d B", size);
         }
     }

+ 39 - 27
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java

@@ -1,7 +1,5 @@
 package com.ruoyi.framework.web.domain.server;
 
-import com.ruoyi.common.utils.Arith;
-
 /**
  * CPU相关信息
  * 
@@ -38,6 +36,30 @@ public class Cpu
      * CPU当前空闲率
      */
     private double free;
+    /**
+     * CPU温度
+     */
+    private double temperature;
+    /**
+     * 速度
+     */
+    private double speed;
+
+    public double getSpeed() {
+        return speed;
+    }
+
+    public void setSpeed(double speed) {
+        this.speed = speed;
+    }
+
+    public double getTemperature() {
+        return temperature;
+    }
+
+    public void setTemperature(double temperature) {
+        this.temperature = temperature;
+    }
 
     public int getCpuNum()
     {
@@ -49,53 +71,43 @@ public class Cpu
         this.cpuNum = cpuNum;
     }
 
-    public double getTotal()
-    {
-        return Arith.round(Arith.mul(total, 100), 2);
+    public double getTotal() {
+        return total;
     }
 
-    public void setTotal(double total)
-    {
+    public void setTotal(double total) {
         this.total = total;
     }
 
-    public double getSys()
-    {
-        return Arith.round(Arith.mul(sys / total, 100), 2);
+    public double getSys() {
+        return sys;
     }
 
-    public void setSys(double sys)
-    {
+    public void setSys(double sys) {
         this.sys = sys;
     }
 
-    public double getUsed()
-    {
-        return Arith.round(Arith.mul(used / total, 100), 2);
+    public double getUsed() {
+        return used;
     }
 
-    public void setUsed(double used)
-    {
+    public void setUsed(double used) {
         this.used = used;
     }
 
-    public double getWait()
-    {
-        return Arith.round(Arith.mul(wait / total, 100), 2);
+    public double getWait() {
+        return wait;
     }
 
-    public void setWait(double wait)
-    {
+    public void setWait(double wait) {
         this.wait = wait;
     }
 
-    public double getFree()
-    {
-        return Arith.round(Arith.mul(free / total, 100), 2);
+    public double getFree() {
+        return free;
     }
 
-    public void setFree(double free)
-    {
+    public void setFree(double free) {
         this.free = free;
     }
 }

+ 83 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Gpu.java

@@ -0,0 +1,83 @@
+package com.ruoyi.framework.web.domain.server;
+
+/**
+ * @Author: tjf
+ * @Date: 2025/8/13 星期三 15:19
+ * @Describe:
+ */
+public class Gpu {
+
+    /**
+     * 总显存
+     */
+    private String gpuMemory;
+    /**
+     * 使用率
+     */
+    private String used;
+    /**
+     * 显存使用
+     */
+    private String useMemory;
+    /**
+     * 名称
+     */
+    private String name;
+    /**
+     * 功耗
+     */
+    private String power;
+
+    /**
+     * GPU温度
+     */
+    private String temperature;
+
+    public String getGpuMemory() {
+        return gpuMemory;
+    }
+
+    public void setGpuMemory(String gpuMemory) {
+        this.gpuMemory = gpuMemory;
+    }
+
+    public String getUsed() {
+        return used;
+    }
+
+    public void setUsed(String used) {
+        this.used = used;
+    }
+
+    public String getUseMemory() {
+        return useMemory;
+    }
+
+    public void setUseMemory(String useMemory) {
+        this.useMemory = useMemory;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPower() {
+        return power;
+    }
+
+    public void setPower(String power) {
+        this.power = power;
+    }
+
+    public String getTemperature() {
+        return temperature;
+    }
+
+    public void setTemperature(String temperature) {
+        this.temperature = temperature;
+    }
+}

+ 46 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Network.java

@@ -0,0 +1,46 @@
+package com.ruoyi.framework.web.domain.server;
+
+/**
+ * @Author: tjf
+ * @Date: 2025/8/13 星期三 15:26
+ * @Describe:
+ */
+public class Network {
+    /**
+     * 上行
+     */
+    private double networkUp;
+    /**
+     * 下行
+     */
+    private double networkDown;
+    /**
+     * 名称
+     */
+    private String networkName;
+
+
+    public double getNetworkUp() {
+        return networkUp;
+    }
+
+    public void setNetworkUp(double networkUp) {
+        this.networkUp = networkUp;
+    }
+
+    public double getNetworkDown() {
+        return networkDown;
+    }
+
+    public void setNetworkDown(double networkDown) {
+        this.networkDown = networkDown;
+    }
+
+    public String getNetworkName() {
+        return networkName;
+    }
+
+    public void setNetworkName(String networkName) {
+        this.networkName = networkName;
+    }
+}

+ 1 - 1
ruoyi-system/src/main/resources/mapper/manage/ClockSetMapper.xml

@@ -36,7 +36,7 @@
     <select id="selectClockSetList" parameterType="ClockSet" resultMap="ClockSetResult">
         <include refid="selectClockSetVo"/>
         <where>
-            <if test="clockName != null ">and clock_name = #{clockName}</if>
+            <if test="clockName != null ">and clock_name like concat('%', #{clockName}, '%')</if>
             <if test="clockBeginAm != null ">and clock_begin_am = #{clockBeginAm}</if>
             <if test="clockEndAm != null ">and clock_end_am = #{clockEndAm}</if>
             <if test="clockBeginPm != null ">and clock_begin_pm = #{clockBeginPm}</if>

+ 1 - 0
ruoyi-ui/package.json

@@ -45,6 +45,7 @@
     "clipboard": "2.0.8",
     "core-js": "3.37.1",
     "echarts": "^5.1.0",
+    "echarts-liquidfill": "^3.1.0",
     "element-ui": "2.15.14",
     "file-saver": "2.0.5",
     "flv": "^0.0.1",

BIN
ruoyi-ui/src/assets/images/icon_gjht_wl_ss.png


BIN
ruoyi-ui/src/assets/images/icon_gjht_wl_xj.png


+ 328 - 0
ruoyi-ui/src/views/system/service/index.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="app-container">
+    <div class="flexcw" :class="{'toplinea':server&&server.netWorkList&&server.netWorkList.length>2}">
+      <div class="topa flex1 carboxs" v-if="server.sys">
+        <div class="cardbox">
+          <div class="titbox">服务器信息</div>
+          <div class="topbox flexc">
+            <div class="txts">
+              <div class="tit">服务器名称</div>
+              <div class="txt">{{server.sys.computerName}}</div>
+            </div>
+            <div class="txts">
+              <div class="tit">操作系统</div>
+              <div class="txt">{{ server.sys.osName }}</div>
+            </div>
+            <div class="txts">
+              <div class="tit">服务器IP</div>
+              <div class="txt">{{ server.sys.computerIp }}</div>
+            </div>
+            <div class="txts">
+              <div class="tit">系统架构</div>
+              <div class="txt">{{ server.sys.osArch }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <template v-if="server&&server.netWorkList&&server.netWorkList.length">
+        <div class="carboxs topb" v-for="(ite,idx) in server.netWorkList" :key="idx">
+          <div class="cardbox" >
+            <div class="titbox">网络占用<span>({{ite.networkName}})</span></div>
+            <div class="topbox flexc ">
+              <div class="txts">
+                <div class="tit">下行</div>
+                <div class="txt">{{ite.networkDown}}KB/S</div>
+              </div>
+              <div class="txts">
+                <div class="tit">上行</div>
+                <div class="txt">{{ ite.networkUp }}KB/S</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </template>
+
+    </div>
+    <div class="flexcw">
+      <div class="carboxs boxb" style="min-width: 40%;">
+        <div class="cardbox ">
+          <div class="titbox">系统内存</div>
+          <div class="flexc" v-if="server.mem">
+            <div class="waterbox" >
+              <water-ball-chart :chartData="server.mem" :waterColors="waterColora"></water-ball-chart>
+            </div>
+            <div class="watxtbox">
+              <div class="txts">
+                <div class="tit">剩余内存</div>
+                <div class="txt">{{server.mem.free}}G</div>
+              </div>
+              <div class="txts">
+                <div class="tit">已用内存</div>
+                <div class="txt">{{server.mem.used}}G</div>
+              </div>
+              <div class="txts">
+                <div class="tit">总内存</div>
+                <div class="txt">{{server.mem.total}}G</div>
+              </div>
+              <div class="txts">
+                <div class="tit">使用率</div>
+                <div class="txt">{{server.mem.usage}}%</div>
+              </div>
+            </div>
+          </div>
+          <div class="titbox" style="margin-top: 9px;">JVM内存</div>
+          <div class="flexc"  v-if="server.jvm">
+            <div class="waterbox">
+              <water-ball-chart :chartData="server.jvm" :waterColors="waterColorb"></water-ball-chart>
+            </div>
+            <div class="watxtbox">
+              <div class="txts">
+                <div class="tit">剩余内存</div>
+                <div class="txt">{{server.jvm.free}}M</div>
+              </div>
+              <div class="txts">
+                <div class="tit">已用内存</div>
+                <div class="txt">{{server.jvm.used}}M</div>
+              </div>
+              <div class="txts">
+                <div class="tit">总内存</div>
+                <div class="txt">{{server.jvm.total}}M</div>
+              </div>
+              <div class="txts">
+                <div class="tit">使用率</div>
+                <div class="txt">{{server.jvm.usage}}%</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- cpu -->
+      <template v-if="server.cpuList&&server.cpuList.length">
+        <div class="carboxs boxb" style="min-width: 30%;" v-for="(cpuite,idx) in server.cpuList" :key="'cpu'+idx">
+          <div class="cardbox " >
+            <div class="titbox titboxa">CPU使用率 {{idx<9?'0'+(idx+1):idx+1}}</div>
+            <gaugea-chart v-if="cpuite" :chartData="cpuite"></gaugea-chart>
+            <div class="flexcw txtboxc">
+              <div class="txtcs">
+                <div class="tit">用户使用率</div>
+                <div class="txt">{{ cpuite.used }} %</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">系统使用率</div>
+                <div class="txt">{{ cpuite.sys }} %</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">当前空闲率</div>
+                <div class="txt">{{ cpuite.free }} %</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">核心数</div>
+                <div class="txt">{{ cpuite.cpuNum }}</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">温度</div>
+                <div class="txt flexc">{{cpuite.temperature}} ℃
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+      </template>
+      <template v-if="server.gpuList&&server.gpuList.length">
+        <!-- gpu -->
+        <div class="carboxs boxb" style="min-width: 30%;" v-for="(gpuite,idx) in server.gpuList" :key="'gpu'+idx">
+          <div class="cardbox " >
+            <div class="titbox titboxa">GPU使用率 {{idx<9?'0'+(idx+1):idx+1}}<span>({{gpuite.name}})</span></div>
+            <gaugeb-chart  v-if="gpuite" :chartData="gpuite"></gaugeb-chart>
+            <div class="flexcw txtboxc">
+              <div class="txtcs">
+                <div class="tit">总显存</div>
+                <div class="txt">{{gpuite.gpuMemory}}</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">使用率</div>
+                <div class="txt">{{gpuite.used}}</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">显存使用</div>
+                <div class="txt">{{gpuite.useMemory}}</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">温度</div>
+                <div class="txt">{{gpuite.temperature}}</div>
+              </div>
+              <div class="txtcs">
+                <div class="tit">功耗</div>
+                <div class="txt">{{gpuite.power}}</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </template>
+    </div>
+    <el-row :gutter="14">
+      <el-col :span="24" class="card-box">
+        <div class="cardbox">
+          <div class="titbox">磁盘状态</div>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <thead>
+                <tr>
+                  <th class="el-table__cell el-table__cell is-leaf"><div class="cell">盘符路径</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">文件系统</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">盘符类型</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">总大小</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">可用大小</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">已用大小</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">已用百分比</div></th>
+                </tr>
+              </thead>
+              <tbody v-if="server.sysFiles">
+                <tr v-for="(sysFile, index) in server.sysFiles" :key="index">
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.dirName }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.sysTypeName }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.typeName }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.total }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.free }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.used }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { getServer } from "@/api/monitor/server"
+ import waterBallChart from '../servicechart/waterBallChart'
+ import gaugeChart from '../servicechart/gaugeChart'
+ import gaugeaChart from '../servicechart/gaugeaChart'
+ import gaugebChart from '../servicechart/gaugebChart'
+export default {
+  name: "service",
+  components:{waterBallChart,gaugeChart,gaugeaChart,gaugebChart},
+  data() {
+    return {
+      // 服务器信息
+      server: {},
+      waterColora:['rgba(67, 129, 255, 1)','rgba(237, 244, 255, 1)'],
+      waterColorb:['rgba(27, 210, 158, 1)','rgba(237, 244, 255, 1)'],
+      timer:null
+    }
+  },
+  created() {
+    clearInterval(this.timer)
+    var that=this;
+    that.getList()
+    this.timer=setInterval(()=>{
+      that.getList()
+    },10000)
+    this.openLoading()
+  },
+  destroyed() {
+    clearInterval(this.timer)
+  },
+  mounted() {
+  },
+  methods: {
+    /** 查询服务器信息 */
+    getList() {
+      getServer().then(response => {
+        if(response.code!=200){
+          clearInterval(this.timer)
+        }
+        this.server = JSON.parse(JSON.stringify(response.data));
+        this.$modal.closeLoading()
+      })
+    },
+    // 打开加载层
+    openLoading() {
+      this.$modal.loading("正在加载服务监控数据,请稍候!")
+    },
+  }
+}
+</script>
+<style lang="scss" scoped>
+.card-box{padding: 0 7px !important;}
+.mb14{margin-bottom: 14px;}
+.cardbox ::v-deep{
+  .el-table::before{display: none;}
+  .el-table thead{color: #333;
+    th{border-bottom: 1px solid #E6E6E6 !important;}
+  }
+  .el-table th.el-table__cell.is-leaf,.el-table td.el-table__cell{border-bottom: none;}
+}
+  .flexc{display: flex;align-items: center;}
+  .flex0{flex: 0 0 auto;}
+  .flex1{flex: 1;}
+  .flex{display: flex;}
+  .w50{width: 50%;}
+  .flexcw{display: flex;flex-wrap: wrap;}
+.card-box{margin-bottom: 14px !important;}
+.toplinea{
+    .topa{width: 100%;flex: 0 0 auto;}
+    .topb{flex: 1;}
+  }
+.carboxs{padding: 0 7px 0px;margin-bottom: 14px;box-sizing: border-box;
+    &.topb{//信息
+      .topbox{flex: 1;}
+      .txts{padding-left: 8px !important;}
+    }
+    &.boxb{//cpu
+       min-height: 444px;flex: 1;
+       .titboxa{min-height:44px;}
+    }
+}
+.cardbox{
+  background: #FFFFFF;border-radius: 10px;padding: 24px 0 0;box-sizing: border-box;
+  .el-table{padding: 0 35px 10px;margin-top: 10px;color: #333}
+  .topbox{flex-wrap: wrap;padding: 16px 14px;margin-top: 2px;
+    .txts{min-width: 25%;display: flex;flex-direction: column;position: relative;padding: 13px 0px 13px 20px;box-sizing: border-box;flex: 1 0 auto;
+      .tit{font-weight: 500;font-size: 14px;color: #3D455B;margin-bottom: 12px;}
+      .txt{font-weight: bold;font-size: 16px;color: #333333;}
+    }
+  }
+  .waterbox{width: 48%;flex: 0 0 auto;}
+  .watxtbox{flex: 1;display: flex;flex-wrap: wrap;
+    .txts{width: 50%;flex: 0 0 auto;padding: 11px 6px;box-sizing: border-box;
+      .tit{font-weight: 500;font-size: 14px;color: #3D455B;margin-bottom: 8px;}
+      .txt{font-size: 20px;color: #333333;font-weight: bold;}
+    }
+  }
+  .guttit{margin: 0 auto;padding: 10px 0;max-width: 300px;
+      .tit{font-size: 18px;color: #3D455B;width: 33.3%;flex: 0 0 auto;display: flex;align-items: center;justify-content: center;
+        .txt{font-size: 14px;margin-left: 8px;color: #AAAAAA;}
+        img{width: 8px;height: 14px;margin-right: 7px;}
+      }
+      .num{font-size: 20px;color: #333333;flex: 1;text-align: center;
+        span{font-size: 16px;color: #333333;margin-left: 6px;}
+      }
+  }
+  .txtboxc{padding: 14px 17px 10px;
+    .txtcs{min-width: 33.3%;box-sizing: border-box;
+        display: flex;flex-wrap: wrap;flex: 0 0 auto;padding: 11px 0 11px 10px;flex-direction: column;
+        .tit{font-weight: 500;font-size: 14px;color: #3D455B;margin-bottom: 9px;}
+        .txt{font-size: 20px;color: #333333;font-weight: bold;
+          .txtbtn{display: inline-block;margin-left: 10px;height: 20px;padding: 0 6px;
+    font-weight: 500;font-size: 12px;color: #FFFFFF;border-radius: 4px;line-height: 20px;
+            &.bga{background: #45CB99;}
+            &.bgb{background: #FFCC51;}
+            &.bgc{background: #FD666D;}
+    }
+        }
+      }
+  }
+
+}
+.titbox{font-weight: bold;font-size: 16px;color: #333333;padding-left: 14px;position: relative;line-height: 22px;
+  &::before{content: '';left: 0;height: 20px;top: 1px;background-color: #03BF8A;width: 6px;position: absolute;}
+  span{font-size: 16px;color: #AAAAAA;font-weight: 400;}
+}
+</style>

+ 138 - 0
ruoyi-ui/src/views/system/servicechart/gaugeChart.vue

@@ -0,0 +1,138 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import * as echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '140px'
+    },
+    chartData: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.chartData)
+    },
+    setOptions({ } = {}) {
+      this.chart.setOption({
+         // 配置项
+        series: [{
+              type: 'gauge',
+              center: ["50%", "90%"], //调整位置
+              radius: '150%', //外圆半径55
+              startAngle: 180,
+              endAngle: 0, // 结束位置
+              progress: {
+                show: true,
+                width: 13,
+                color:'#6296FF',
+              },
+              pointer: {
+                length: "60%",
+                width: 5,
+                itemStyle: {
+                  type: 'linear',
+                    color: '#6296FF' // 修改指针颜色
+                }
+              },
+              itemStyle:{
+                normal: {
+                  //具体颜色显示
+                    color: '#6296FF',
+                  },
+              },
+              axisLine: {
+                lineStyle: {
+                  width: 10,
+                  color: [
+                    [1, '#E8EBF0']
+                  ]
+                }
+              },
+              axisTick: {
+                show: false
+              },
+              splitLine: {
+                length: 6,
+                distance: 8,
+                lineStyle: {
+                  width: 1,
+                  color: '#999'
+                }
+              },
+              axisLabel: {
+                distance: 14,
+                color: '#999',
+                fontSize: 12
+              },
+              anchor: {
+                show: true,
+                showAbove: true,
+                size: 12,
+                itemStyle: {
+                  borderWidth: 6,
+                  borderColor: "#6296FF"
+                }
+              },
+              title: {
+                show: false
+              },
+              detail: {
+                valueAnimation: true,
+                fontSize: 32,
+                offsetCenter: [0, '50%']
+              },
+              data: [
+                {
+                  value: 40
+                }
+              ]
+            }]
+      })
+    }
+  }
+}
+</script>

+ 144 - 0
ruoyi-ui/src/views/system/servicechart/gaugeaChart.vue

@@ -0,0 +1,144 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import * as echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    },
+    chartData: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.chartData)
+    },
+    setOptions({ speed} = {}) {
+      const val=speed||0
+      this.chart.setOption({
+         // 配置项
+        series: [{
+      type: 'gauge',
+      radius: "100%",
+      center: ["50%", "50%"], //调整位置
+      axisLine: {
+        lineStyle: {
+          width: 16,
+          color: [
+            [0.3, '#69E5AC'],
+            [0.7, '#6296FF'],
+            [1, '#FFCC51']
+          ]
+        }
+      },
+      pointer: {
+        itemStyle: {
+          color: 'auto'
+        }
+      },
+      axisTick: {
+        distance: -16,
+        length: 6,
+        splitNumber: 5,
+        lineStyle: {
+          color: '#fff',
+          width: 1
+        }
+      },
+      splitLine: {
+        distance: -30,
+        length: 30,
+        lineStyle: {
+          color: '#fff',
+          width: 4
+        }
+      },
+      axisLabel: {
+        color: 'inherit',
+        distance: 22,
+        fontSize: 12
+      },
+      detail: {
+        valueAnimation: true,
+        formatter: '{value} GHz',
+        color: '#AAAAAA',
+        fontSize: 16,
+        fontWeight: '400',
+         offsetCenter: [0, "60%"], // 居中显示(默认值)
+        formatter: function(value) {
+          // 使用富文本标签实现换行和不同样式
+              return [
+                  '{a|' + val +'}'+'GHz',
+                  // '{b|正常}'
+              ].join('\n');
+          },
+          // 定义富文本样式
+          rich: {
+              a: {
+                  fontSize: 22,
+                  color: '#333333',
+                  align: 'center',
+                  padding: [0, 5, 0, 0]
+              },
+              b: {
+                  fontSize: 16,
+                  fontWeight: '500',
+                  color: '#666666',
+                  align: 'center',
+              },
+          }
+      },
+      data: [
+        {
+          value: val
+        }
+      ]
+    }]
+      })
+    }
+  }
+}
+</script>

+ 147 - 0
ruoyi-ui/src/views/system/servicechart/gaugebChart.vue

@@ -0,0 +1,147 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import * as echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '200px'
+    },
+    chartData: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.chartData)
+    },
+    setOptions({ used} = {}) {
+      var val=used?used.split('%')[0]:0
+      this.chart.setOption({
+         // 配置项
+        series: [{
+      type: 'gauge',
+      radius: "100%",
+      center: ["50%", "50%"], //调整位置
+      axisLine: {
+        lineStyle: {
+          width: 16,
+          color: [
+            [0.3, '#67E0E3'],
+            [0.7, '#37A2DA'],
+            [1, '#FD666D']
+          ]
+        }
+      },
+      pointer: {
+        itemStyle: {
+          color: 'auto'
+        }
+      },
+      axisTick: {
+        distance: -16,
+        length: 6,
+        splitNumber: 5,
+        lineStyle: {
+          color: '#fff',
+          width: 1
+        }
+      },
+      splitLine: {
+        distance: -30,
+        length: 30,
+        lineStyle: {
+          color: '#fff',
+          width: 4
+        }
+      },
+      axisLabel: {
+        color: 'inherit',
+        distance: 22,
+        fontSize: 12
+      },
+      detail: {
+        valueAnimation: true,
+        formatter: used,
+        color: '#AAAAAA',
+        fontSize: 16,
+        fontWeight: '400',
+         offsetCenter: [0, "60%"], // 居中显示(默认值)
+        formatter: function(value) {
+          // 使用富文本标签实现换行和不同样式
+              return [
+                  '{a|' + val +'}'+'%',
+              ].join('\n');
+              // return [
+              //     '{a|' + value +'}'+'GHz',
+              //     '{b|正常}'
+              // ].join('\n');
+          },
+          // 定义富文本样式
+          rich: {
+              a: {
+                  fontSize: 22,
+                  color: '#333333',
+                  align: 'center',
+                  padding: [0, 5, 0, 0]
+              },
+              b: {
+                  fontSize: 16,
+                  fontWeight: '500',
+                  color: '#666666',
+                  align: 'center'
+              },
+          }
+      },
+      data: [
+        {
+          value: val
+        }
+      ]
+    }]
+      })
+    }
+  }
+}
+</script>

+ 56 - 0
ruoyi-ui/src/views/system/servicechart/mixins/resize.js

@@ -0,0 +1,56 @@
+import { debounce } from '@/utils'
+
+export default {
+  data() {
+    return {
+      $_sidebarElm: null,
+      $_resizeHandler: null
+    }
+  },
+  mounted() {
+    this.initListener()
+  },
+  activated() {
+    if (!this.$_resizeHandler) {
+      // avoid duplication init
+      this.initListener()
+    }
+
+    // when keep-alive chart activated, auto resize
+    this.resize()
+  },
+  beforeDestroy() {
+    this.destroyListener()
+  },
+  deactivated() {
+    this.destroyListener()
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_sidebarResizeHandler(e) {
+      if (e.propertyName === 'width') {
+        this.$_resizeHandler()
+      }
+    },
+    initListener() {
+      this.$_resizeHandler = debounce(() => {
+        this.resize()
+      }, 100)
+      window.addEventListener('resize', this.$_resizeHandler)
+
+      this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
+      this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
+    },
+    destroyListener() {
+      window.removeEventListener('resize', this.$_resizeHandler)
+      this.$_resizeHandler = null
+
+      this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
+    },
+    resize() {
+      const { chart } = this
+      chart && chart.resize()
+    }
+  }
+}

+ 123 - 0
ruoyi-ui/src/views/system/servicechart/waterBallChart.vue

@@ -0,0 +1,123 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import * as echarts from 'echarts'
+require('echarts/theme/macarons') // echarts theme
+import resize from './mixins/resize'
+import 'echarts-liquidfill'; // 引入液体填充插件
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '182px'
+    },
+    chartData: {
+      type: Object,
+      required: true
+    },
+    waterColors:{
+      type: Array,
+      default: ['rgba(67, 129, 255, 1)','rgba(237, 244, 255, 1)']
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    },
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.chartData)
+    },
+    setOptions({ usage } = {}) {
+      const datanum=usage/100||0;
+      const val=usage||0
+      // 定义渐变颜色 - 水的颜色
+      const waterColor = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+           { offset: 0, color: this.waterColors[0] },   // 上部颜色
+          { offset: 1, color: this.waterColors[1] }   // 下部颜色
+       ]);
+      this.chart.setOption({
+         // 配置项
+        series: [{
+          type: 'liquidFill',//设置图表类型
+          data: [datanum], // 设置水位,值为0到1之间
+          radius: '80%', // 设置图的大小
+          outline: {
+            borderDistance: 4, // 外边框距离
+            itemStyle: {
+              borderWidth: 8, // 外边框宽度
+              borderColor: '#F6F6F6' ,// 外边框颜色
+              shadowBlur:0,// 阴影模糊度
+            }
+          },
+          color: [waterColor],
+          backgroundStyle: {
+            color: '#F4F7FE' ,// 背景色
+          },
+          label: {
+            formatter: function(value) {
+              // 使用富文本标签实现换行和不同样式
+                  return [
+                      '{a|' + val +'}{b|%}',
+                      // '{b|正常}'
+                  ].join('\n');
+              },
+              // 定义富文本样式
+              rich: {
+                  a: {
+                      fontSize: 22,
+                      color: '#333333',
+                      align: 'center',
+                      padding: [0, 9, 10, 0]
+                  },
+                  b: {
+                      fontSize: 16,
+                      color: '#333333',
+                  },
+                  c: {
+                      fontSize: 16,
+                      fontWeight: '500',
+                      color: '#666666',
+                      align: 'center'
+                  },
+              }
+          }
+        }]
+      })
+    }
+  }
+}
+</script>