Java如何獲取主機的基本信息詳解
最近在做一個主機資源監(jiān)控的需求,首先是獲取一些最簡單的基本參,像一些主機名稱、系統(tǒng)類型、ip、cpu、內(nèi)存和磁盤等等這些數(shù)據(jù),看起來雖然很簡單,Java的基本庫就能完成,但是真的去使用的時候,還是有一些坑的。記錄一下,已備后用。
1. 獲取基本信息
1.1 獲取主機名稱和系統(tǒng)
主機名稱可以通過網(wǎng)絡(luò)類InetAddress來獲取,主機系統(tǒng)和用戶可以通過System類進行獲取。
public static void getLocalHost(){ try{ InetAddress ip = InetAddress.getLocalHost(); String localName = ip.getHostName(); String osName = System.getProperty("os.name"); String userName = System.getProperty("user.name"); String osVersion = System.getProperty("os.version"); String osArch = System.getProperty("os.arch"); System.out.println("當(dāng)前用戶:" + userName); System.out.println("用戶的主目錄:"+props.getProperty("user.home")); System.out.println("用戶的當(dāng)前工作目錄:"+props.getProperty("user.dir")); System.out.println("主機名稱:" + localName); System.out.println("主機系統(tǒng):" + osName); System.out.println("系統(tǒng)版本:" + osVersion); System.out.println("系統(tǒng)架構(gòu):" + osArch); } catch (Exception e) { e.printStackTrace(); } }
1.2 獲取用戶信息
用戶信息都是使用System類進行獲取。
public static void getUserInfo(){ try{ String userName = System.getProperty("user.name"); String userHome = System.getProperty("user.home"); String userDir = System.getProperty("user.dir"); System.out.println("當(dāng)前用戶:" + userName); System.out.println("用戶主目錄:"+ userHome); System.out.println("當(dāng)前工作目錄:"+ userDir); } catch (Exception e) { e.printStackTrace(); } }
1.3 獲取主機IP等信息
主機的ip可以通過網(wǎng)絡(luò)類InetAddress進行獲取,但是這個方法很玄學(xué),機器上多網(wǎng)卡還有虛擬機時,獲取到就不準(zhǔn)確了。目前做的獲取的方法是痛毆便利網(wǎng)卡來獲取ip。因為遍歷網(wǎng)卡來獲取ip要過濾一些不重要的網(wǎng)卡,過濾的方法是來自“經(jīng)驗”的笨方法,可以借鑒,但不保證日后網(wǎng)卡條件復(fù)雜的情況下獲取不準(zhǔn)確。測試的是Linux、Mac和Windows系統(tǒng)可用。因為過濾條件不一樣,所以分為Windows獲取和非Windows獲取。
Windows系統(tǒng)獲取IP:
public static void getWindowsIpAndMac(){ try { Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); // 遍歷網(wǎng)卡接口 while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = allNetInterfaces.nextElement(); // 去除回環(huán)接口,子接口,未運行和接口 if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) { continue; } // 重點來了:“經(jīng)驗”之談 // 為了過濾掉虛擬機的網(wǎng)卡,可以通過網(wǎng)卡名來進行基礎(chǔ)過濾。windows主機ip對應(yīng)的網(wǎng)卡名會包含下面三個:Intel 無線、Realtek 網(wǎng)線、Ethernet 兼容xp系統(tǒng) if (!netInterface.getDisplayName().contains("Intel") && !netInterface.getDisplayName().contains("Realtek") && !netInterface.getDisplayName().contains("Ethernet")) { continue; } String ip = ""; String mac = ""; String niName = ""; Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ia = addresses.nextElement(); // 去除本地回環(huán)地址,子接口,未運行和地址 if (ia != null && !ia.isLoopbackAddress() && ia.isSiteLocalAddress() && !ia.isAnyLocalAddress()) { // 判斷是否是ip v4地址 if (ia instanceof Inet4Address) { ip = ia.getHostAddress(); // 獲取MAC地址 mac = getMac(ia); niName = netInterface.getName(); if (StringUtils.isNotBlank(ip) && StringUtils.isNotBlank(mac) && StringUtils.isNotBlank(niName)){ System.out.println("當(dāng)前網(wǎng)卡:"+niName); System.out.println("當(dāng)前主機ip:"+ip); System.out.println("當(dāng)前主機MAC:"+mac); return; } } } } } } catch (SocketException e) { e.printStackTrace(); } }
非Windows系統(tǒng)獲取IP:
其實和windows獲取的差不多,也是遍歷網(wǎng)卡然后進行過濾,不過這個沒有“經(jīng)驗”,不知道要過濾那些,所以用InetAddress進行獲取,經(jīng)測試這個在非windows上獲取的還是準(zhǔn)確的(可能我linux網(wǎng)卡單一)。不過為了獲取當(dāng)前的網(wǎng)卡用了一個更笨的方法,既然當(dāng)前獲取的ip是準(zhǔn)確的,那就根據(jù)ip去獲取網(wǎng)卡。不過目前沒有找到這個方法,所以可以在遍歷網(wǎng)卡時取出符合當(dāng)前ip的網(wǎng)卡。(此方法在我這個需求里是可以的,不保證拿走就能用)。
public static void getLinuxIpAndMac(AgentMonitor agentMonitor){ try { // 先獲取ip InetAddress iad = InetAddress.getLocalHost(); String localIp = iad.getHostAddress(); ? // 遍歷網(wǎng)卡 Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = allNetInterfaces.nextElement(); // 去除回環(huán)接口,子接口,未運行和接口 if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) { continue; } ? String ip = ""; String mac = ""; String niName = ""; Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ia = addresses.nextElement(); if (ia != null && !ia.isLoopbackAddress() && ia.isSiteLocalAddress() && !ia.isAnyLocalAddress()) { // 判斷是否是ip v4地址且是否和已獲取的ip一致 if (ia instanceof Inet4Address && ia.getHostAddress().equals(localIp)) { ip = ia.getHostAddress(); // 獲取MAC地址 mac = getMac(ia); niName = netInterface.getName(); if (StringUtils.isNotBlank(ip) && StringUtils.isNotBlank(mac) && StringUtils.isNotBlank(niName)){ System.out.println("當(dāng)前網(wǎng)卡:"+niName); System.out.println("當(dāng)前主機ip:"+ip); System.out.println("當(dāng)前主機MAC:"+mac); return; } } } } } ? } catch (Exception e) { e.printStackTrace(); } }
獲取MAC地址
public static String getMac(InetAddress ia){ try { //獲取網(wǎng)卡,獲取地址 byte[] mac = NetworkInterface.getByInetAddress(ia).getHardwareAddress(); StringBuffer sb = new StringBuffer(); if (mac != null && mac.length>0){ for(int i=0; i<mac.length; i++) { if(i!=0) { sb.append("-"); } //字節(jié)轉(zhuǎn)換為整數(shù) String str = Integer.toHexString(mac[i] & 0xff); if(str.length()==1) { sb.append("0").append(str); }else { sb.append(str); } } } return sb.toString().toUpperCase(); } catch (SocketException e) { e.printStackTrace(); return null; } }
2. 獲取CPU信息
獲取CPU的信息這里選用的是oshi工具,經(jīng)測試這個獲取的還是比較準(zhǔn)確的,而且該工具還可以獲得其他硬件信息,能獲取到的還是比較全面的。首先需要引入oshi的依賴。
<dependency> <groupId>com.github.oshi</groupId> <artifactId>oshi-core</artifactId> <version>3.12.2</version> </dependency>
oshi是依賴于JNA,需要導(dǎo)入jna和jna-platform我這里用的oshi是3.12.2版本,對應(yīng)使用的JNA的版本是5.2.0。springboot項目是自帶JNA的,如果不是springboot項目需要額外導(dǎo)入。如果springboot項目自帶的JNA版本過低,也需要額外導(dǎo)入高版本的JNA。
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.2.0</version>
</dependency>
JNA版本信息
2.1 獲取CPU核數(shù)
oshi中的CentralProcessor進行獲取。獲取CPU物理可用的核數(shù),如果有開啟超頻,那么獲取的CPU核數(shù)可能會大于物理核數(shù)。
public static void getCpuCount(){ try { // 獲取SystemInfo實例 SystemInfo systemInfo = new SystemInfo(); // 獲取CentralProcessor實例 CentralProcessor processor = systemInfo.getHardware().getProcessor(); // 獲取CPU核數(shù) int cpuCount = processor.getLogicalProcessorCount(); System.out.println("CPU核數(shù):"+cpuCount); } catch (SocketException e) { e.printStackTrace(); } }
2.2 獲取CPU使用率
獲取系統(tǒng)范圍的CPU負載時,一共獲取7個部分的負載。
- CPU 空閑且系統(tǒng)沒有未完成的磁盤 I/O 請求的時間。
- 在系統(tǒng)有未完成的磁盤 I/O 請求期間一個或多個 CPU 空閑的時間。在windows不可用。在MacOS不可用。
- CPU 用于服務(wù)硬件 IRQ 的時間。在MacOS不可用。
- 在具有良好優(yōu)先級的用戶級別執(zhí)行時發(fā)生的 CPU 利用率。在windows不可用。
- CPU 用于服務(wù)軟 IRQ 的時間。
- 管理程序?qū)S糜谙到y(tǒng)中其他來賓的時間。
- 在系統(tǒng)級別(內(nèi)核)執(zhí)行時發(fā)生的 CPU 利用率。
- 在用戶級別(應(yīng)用程序)執(zhí)行時發(fā)生的 CPU 使用率。
要使用此方法計算總體空閑時間,就要包括上面所有部分,這樣計算出來的結(jié)果更準(zhǔn)確且兼容各種平臺。分兩次獲取上面信息,間隔1秒。這樣就能計算出1秒的CPU各方面使用的差值,通過每一項的差值除以總量,便可以得到每一項的CPU使用率。
通過下面方法還可以獲得CPU時間間隔內(nèi)的使用率和總使用率。
public static void getCpuInfo() { try { SystemInfo systemInfo = new SystemInfo(); CentralProcessor processor = systemInfo.getHardware().getProcessor(); // 獲取系統(tǒng)范圍的cpu負載技計數(shù) long[] prevTicks = processor.getSystemCpuLoadTicks(); // 睡眠1s TimeUnit.SECONDS.sleep(1); long[] ticks = processor.getSystemCpuLoadTicks(); // 具有良好優(yōu)先級的用戶級別 long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; // 硬件服務(wù) long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; // 軟服務(wù)使用 long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; // 管理程序使用 long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; // 系統(tǒng)使用 long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; // 用戶使用 long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; // 等待使用 long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; // 空閑使用 long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; double sysRate = cSys * 1.0 / totalCpu; double userRate = user * 1.0 / totalCpu; double waitRate = cSys * 1.0 / totalCpu; double idleRate = cSys * 1.0 / totalCpu; double betweenRate = processor.getSystemCpuLoadBetweenTicks(); double cpuLoad = processor.getSystemCpuLoad(); System.out.println("cpu系統(tǒng)使用率:" + new DecimalFormat("#.##%").format(sysRate)); System.out.println("cpu用戶使用率:" + new DecimalFormat("#.##%").format(userRate)); System.out.println("cpu當(dāng)前等待率:" + new DecimalFormat("#.##%").format(waitRate)); System.out.println("cpu當(dāng)前空閑率:" + new DecimalFormat("#.##%").format(idleRate)); // 獲取cpu最近(時間間隔內(nèi))使用率 System.out.println("CPU load: "+ new DecimalFormat("#.##%").format(betweenRate) +"(counting ticks)"); // 獲取cpu使用率 System.out.println("CPU load: "+ new DecimalFormat("#.##%").format(cpuLoad) +"(OS MXBean)"); }catch (Exception e){ e.printStackTrace(); } }
3. 獲取內(nèi)存信息
3.1 獲取主機內(nèi)存
獲取內(nèi)存信息可以使用OperatingSystemMXBean 來獲取。內(nèi)存信息可以獲取到的有內(nèi)存總量和可用內(nèi)存,通過這兩個值在計算出內(nèi)存已經(jīng)使用的量和內(nèi)存的使用率。獲取內(nèi)存信息同樣也可以使用oshi包中的SystemInfo類進行獲取。但是測試時獲取的數(shù)據(jù)沒有OperatingSystemMXBean獲取的更精確。
public static void getMemInfo(){ try { OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); // 總內(nèi)存,單位:字節(jié) long total = osmxb.getTotalPhysicalMemorySize(); // 空閑內(nèi)存,單位:字節(jié) long free = osmxb.getFreePhysicalMemorySize(); // 可用內(nèi)存,單位:字節(jié) long usable = osmxb.getFreePhysicalMemorySize(); // 已使用內(nèi)存,單位:字節(jié) long used = total - free; // 內(nèi)存使用率 double useRate = used * 1.0 / total; System.out.println("總共內(nèi)存:" + new DecimalFormat("#.##").format(total*1.0 / Math.pow(1024,3)) + "G"); System.out.println("空閑內(nèi)存:" + new DecimalFormat("#.##").format(free*1.0 / Math.pow(1024,3)) + "G"); System.out.println("已用內(nèi)存:" + new DecimalFormat("#.##").format(used*1.0 / Math.pow(1024,3)) + "G"); System.out.println("可用內(nèi)存:" + new DecimalFormat("#.##").format(usable*1.0 / Math.pow(1024,3)) + "G"); System.out.println("內(nèi)存使用率:" + new DecimalFormat("#.##%").format(useRate * 100.0)); ? }catch (Exception e){ e.printStackTrace(); } }
3.2 獲取JVM內(nèi)存
獲取JVM的內(nèi)存信息需要使用MemoryMXBean接口中的MemoryUsage類。JVM信息主要是在系統(tǒng)運行時對JVM的使用情況。包括初始的內(nèi)存大小、最大可用的內(nèi)存以及當(dāng)前已經(jīng)使用的內(nèi)存大小。
public static void getJvmMemInfo(){ try { MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); // 椎內(nèi)存使用情況 MemoryUsage memoryUsage = memoryMXBean.getHeapMemoryUsage(); // jvm初始總內(nèi)存,單位:字節(jié) long initTotalMemorySize = memoryUsage.getInit(); // jvm最大可用內(nèi)存,單位:字節(jié) long free = osmxb.getFreePhysicalMemorySize(); // jvm已使用的內(nèi)存,單位:字節(jié) long usable = osmxb.getFreePhysicalMemorySize(); System.out.println("jvm初始總內(nèi)存:" + new DecimalFormat("#.##").format(total*1.0 / Math.pow(1024,3)) + "G"); System.out.println("jvm最大可用內(nèi)存:" + new DecimalFormat("#.##").format(free*1.0 / Math.pow(1024,3)) + "G"); System.out.println("jvm已使用的內(nèi)存:" + new DecimalFormat("#.##").format(used*1.0 / Math.pow(1024,3)) + "G"); ? }catch (Exception e){ e.printStackTrace(); } }
4. 獲取磁盤信息
獲取磁盤的使用情況用的是基礎(chǔ)的File類。首先是從根目錄遍歷所有磁盤信息,通過下面方法獲取磁盤信息。
- file.getTotalSpace() :獲取當(dāng)前磁盤的總內(nèi)存
- file.getFreeSpace() :獲取當(dāng)前磁盤的空閑內(nèi)存
- file.getUsableSpace() :獲取當(dāng)前磁盤的可用內(nèi)存
通過上面獲取的三個參數(shù),可以計算磁盤總的已使用內(nèi)存和當(dāng)前磁盤的內(nèi)存使用率。
在計算每一個磁盤的信息時,通過全局變量統(tǒng)計所有磁盤的信息總和,然后計算出主機總的磁盤內(nèi)存和使用率。
/** * @param RADIX 內(nèi)存進制大小,"經(jīng)驗"之談是:Windows下進制是1024,Mac和Linux是1000 */ public static void getDiskInfo(int RADIX){ // 統(tǒng)計總內(nèi)存 long total = 0; // 統(tǒng)計總空閑 long free = 0; // 統(tǒng)計總可用 long usable = 0; // 統(tǒng)計總已用 long used = 0; // 磁盤總使用 double usedRate = 0.0; try{ ? File[] disks = File.listRoots(); for (File file : disks){ // 統(tǒng)計總量 total += file.getTotalSpace(); free += file.getFreeSpace(); usable += file.getUsableSpace(); used += file.getTotalSpace() - file.getFreeSpace(); String diskPath = file.getPath(); long diskTotal = file.getTotalSpace(); long diskFree = file.getFreeSpace(); long diskUsable = file.getUsableSpace(); long diskUsed = diskTotal - diskFree; double diskUsedRate = diskUsed * 1.0 / diskTotal; System.out.println("磁盤路徑:" + diskPath); System.out.println("總共空間:"+ new DecimalFormat("#.##").format(diskTotal*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("空閑空間:"+ new DecimalFormat("#.##").format(diskFree*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("可用空間:"+ new DecimalFormat("#.##").format(diskUsable*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("已用空間:"+ new DecimalFormat("#.##").format(diskUsed*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("空間使用率:" + new DecimalFormat("#.##%").format(diskUsedRate*100)); } String rootPath = "/"; usedRate = used * 1.0 / total; System.out.println("磁盤根路徑:"+ rootPath); System.out.println("主機總共空間:"+ new DecimalFormat("#.##").format(total*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("主機總空閑空間:"+ new DecimalFormat("#.##").format(free*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("主機總可用空間:"+ new DecimalFormat("#.##").format(usable*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("主機總已用空間:"+ new DecimalFormat("#.##").format(used*1.0 / Math.pow(RADIX,3)) + "G"); System.out.println("主機總使用率:" + new DecimalFormat("#.##%").format(usedRate*100.0)); }catch (Exception e){ e.printStackTrace(); } }
5. 獲取Java環(huán)境信息
這塊就是補充說明了,暫時沒用到,先保留一下,已備后用。
public static void getJavaInfo(){ Properties props=System.getProperties(); System.out.println("Java的運行環(huán)境版本:"+props.getProperty("java.version")); System.out.println("Java的運行環(huán)境供應(yīng)商:"+props.getProperty("java.vendor")); System.out.println("Java供應(yīng)商的URL:"+props.getProperty("java.vendor.url")); System.out.println("Java的安裝路徑:"+props.getProperty("java.home")); System.out.println("Java的虛擬機規(guī)范版本:"+props.getProperty("java.vm.specification.version")); System.out.println("Java的虛擬機規(guī)范供應(yīng)商:"+props.getProperty("java.vm.specification.vendor")); System.out.println("Java的虛擬機規(guī)范名稱:"+props.getProperty("java.vm.specification.name")); System.out.println("Java的虛擬機實現(xiàn)版本:"+props.getProperty("java.vm.version")); System.out.println("Java的虛擬機實現(xiàn)供應(yīng)商:"+props.getProperty("java.vm.vendor")); System.out.println("Java的虛擬機實現(xiàn)名稱:"+props.getProperty("java.vm.name")); System.out.println("Java運行時環(huán)境規(guī)范版本:"+props.getProperty("java.specification.version")); System.out.println("Java運行時環(huán)境規(guī)范供應(yīng)商:"+props.getProperty("java.specification.vender")); System.out.println("Java運行時環(huán)境規(guī)范名稱:"+props.getProperty("java.specification.name")); System.out.println("Java的類格式版本號:"+props.getProperty("java.class.version")); System.out.println("Java的類路徑:"+props.getProperty("java.class.path")); System.out.println("加載庫時搜索的路徑列表:"+props.getProperty("java.library.path")); System.out.println("默認的臨時文件路徑:"+props.getProperty("java.io.tmpdir")); System.out.println("一個或多個擴展目錄的路徑:"+props.getProperty("java.ext.dirs")); System.out.println("文件分隔符:"+props.getProperty("file.separator"));//在 unix 系統(tǒng)中是"/" System.out.println("路徑分隔符:"+props.getProperty("path.separator"));//在 unix 系統(tǒng)中是":" System.out.println("行分隔符:"+props.getProperty("line.separator"));//在 unix 系統(tǒng)中是"/n" System.out.println("用戶的賬戶名稱:"+props.getProperty("user.name }
總結(jié)
到此這篇關(guān)于Java如何獲取主機的基本信息的文章就介紹到這了,更多相關(guān)Java獲取主機信息內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot?Security認證之Redis緩存用戶信息詳解
本文介紹了如何使用Spring Boot Security進行認證,并通過Redis緩存用戶信息以提高系統(tǒng)性能,通過配置RedisUserDetailsManager,我們成功地將用戶信息存儲到了Redis中,并在Spring Security中進行了集成,需要的朋友可以參考下2024-01-01關(guān)于HttpServletRequest獲取POST請求Body參數(shù)的3種方式
這篇文章主要介紹了關(guān)于HttpServletRequest獲取POST請求Body參數(shù)的3種方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11解決SpringBoot webSocket 資源無法加載、tomcat啟動報錯的問題
這篇文章主要介紹了解決SpringBoot webSocket 資源無法加載、tomcat啟動報錯的問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11Java Swing GridBagLayout網(wǎng)格袋布局的實現(xiàn)
這篇文章主要介紹了Java Swing GridBagLayout網(wǎng)格袋布局的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)雙向鏈表功能
這篇文章主要為大家詳細介紹了java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)雙向鏈表功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11