SpringBoot整合sigar实现服务端监控

sigar简介

由于sigar采集的信息时通过底层C语言来执行的,所以需要以下对应的依赖文件【依赖文件依据平台而定】

依赖文件下载地址:

Windows :sigar-amd64-winnt.dll或sigar-x86-winnt.dll

Linux :libsigar-amd64-linux.so或libsigar-x86-linux.so

MacOX :libsigar-universal64-macosx.dylib或libsigar-universal-macosx.dylib

  • sigar可以搜集的信息

CPU信息:包括基本信息(vendor、model、mhz、cacheSize)和统计信息(user、sys、idle、nice、wait)
文件系统信息:包括Filesystem、Size、Used、Avail、Use%、Type
事件信息:类似Service Control Manager
内存信息:物理内存和交换内存的总数、使用数、剩余数;RAM的大小
网络信息:包括网络接口信息和网络路由信息
进程信息:包括每个进程的内存、CPU占用数、状态、参数、句柄
IO信息:包括IO的状态,读写大小等
服务状态信息
系统信息:包括操作系统版本,系统资源限制情况,系统运行时间以及负载,JAVA的版本信息等

springboot整合sigar使用【以Linux系统为例】
  1. 首先pom.xml引入依赖包
<dependency>
    <groupId>org.fusesource</groupId>
    <artifactId>sigar</artifactId>
    <version>1.6.4</version>
</dependency>
  1. 通过controller写个demo
import cn.hutool.core.util.NumberUtil;
import com.nio.commons.common.ResponseResult;
import com.nio.commons.entity.monitor.ResourcePoolMonitorInfo;
import com.nio.commons.enums.common.ResponseCodeEnums;
import com.nio.resourcepool.utils.BigDecimalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hyperic.sigar.CpuPerc;
import org.hyperic.sigar.Mem;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.net.InetAddress;

@Slf4j
@RestController
public class TestController {
    private Sigar sigar = new Sigar();
    private static final String UNIT = "g";

    @GetMapping("/monitor")
    public ResponseResult getRes(){
        return new ResponseResult(ResponseCodeEnums.SUCCESS,monitor("xxxx"));
    }

		public ResourcePoolMonitorInfo monitor(String id) {
        ResourcePoolMonitorInfo monitorInfo = new ResourcePoolMonitorInfo();
        try {
            //设置机器IP地址
            monitorInfo.setIp(InetAddress.getLocalHost().getHostAddress());
            // 内存
            memoryHandle(monitorInfo);
            //cpu
            cpuHandle(monitorInfo);
            return monitorInfo;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取机器总内存、可使用内存、已使用内存,并格式化
     * @param monitorInfo
     * @throws SigarException
     */
    private void memoryHandle(ResourcePoolMonitorInfo monitorInfo) throws SigarException {
        Mem mem = sigar.getMem();
        long total = mem.getTotal();
        long free = mem.getFree();
        long used = mem.getUsed();
        monitorInfo.setMemTotal(this.getCountByUnit(total, UNIT));
        monitorInfo.setMemUsed(this.getCountByUnit(used, UNIT));
        monitorInfo.setMemFree(this.getCountByUnit(free, UNIT));
        monitorInfo.setMemUsedRate(getPercent(used, total));
        monitorInfo.setMemFreeRate(getPercent(free, total));
    }
    /**
     * 获取机器cpu核数、cpu使用率、cpu用户使用率、cpu系统使用率、cpu空闲率
     * cpu使用率 = cpu用户使用率 + cpu系统使用率
     *
     * @param monitorInfo
     * @throws SigarException
     */
    private void cpuHandle(ResourcePoolMonitorInfo monitorInfo) throws SigarException {
        CpuPerc cpuPerc = sigar.getCpuPerc();
        org.hyperic.sigar.CpuInfo[] cpuInfoList = sigar.getCpuInfoList();
        double user = cpuPerc.getUser();
        double sys = cpuPerc.getSys();
        String userPercent = getPercent(user, 1);
        String sysPercent = getPercent(sys, 1);
        String totalPercent = NumberUtil.add(userPercent, sysPercent).toString();
        BigDecimal idleDecimal = NumberUtil.sub("100", totalPercent);
        String idleRate = NumberUtil.decimalFormat("#.#", idleDecimal);
        monitorInfo.setCpuCore(String.valueOf(cpuInfoList.length));
        monitorInfo.setCpuUseRate(userPercent);
        monitorInfo.setCpuSysRate(sysPercent);
        monitorInfo.setCpuIdleRate(idleRate);
        monitorInfo.setCpuTotalRate(totalPercent);
    }
    /**
     * 单位转化 G M K
     *
     * @param unit
     * @return
     */
    private String getCountByUnit(Long param, String unit) {
        double c = 1024d;
        double count = 0d;
        double num = 0d;

        if (StringUtils.isEmpty(unit)) {
            unit = "m";
        }
        try {
            if ("g".equalsIgnoreCase(unit)) {
                count = BigDecimalUtil.mul(c, BigDecimalUtil.mul(c, c));
            } else if ("m".equalsIgnoreCase(unit)) {
                count = BigDecimalUtil.mul(c, c);
            } else if ("k".equalsIgnoreCase(unit)) {
                count = c;
            }
            num = BigDecimalUtil.div(param.doubleValue(), count, 2);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return NumberUtil.decimalFormat("#.#", num);
    }

    private String getPercent(Number num1, Number num2) {
        Number div = NumberUtil.div(num1, num2);
        Number mul = NumberUtil.mul(div, 100);
        return NumberUtil.decimalFormat("#.#", mul);
    }
}
  1. ResourcePoolMonitorInfoclass文件内容
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResourcePoolMonitorInfo {
    /**
     * IP地址
     */
    private String ip;
    /**
     * 内存总量
     */
    private String memTotal;
    /**
     * 当前内存使用量
     */
    private String memUsed;
    /**
     * 当前内存使用率
     */
    private String memUsedRate;
    /**
     * 当前内存剩余量
     */
    private String memFree;
    /**
     * 当前内存剩余率
     */
    private String memFreeRate;
    /**
     * CPU的核数
     */
    private String cpuCore;
    /**
     * CPU用户使用率
     */
    private String cpuUseRate;
    /**
     * CPU系统使用率
     */
    private String cpuSysRate;
    /**
     * CPU当前空闲率
     */
    private String cpuIdleRate;
    /**
     * CPU总的使用率
     */
    private String cpuTotalRate;
}
  1. libsigar-amd64-linux.solibsigar-x86-linux.so两个文件部署到linux下/usr/lib目录下

可以通过System.getProperty("java.library.path");查看java库的安装路径

在这里插入图片描述
5. 请求:http://ip:port/monitor 查看响应内容
在这里插入图片描述

  1. 返回指标与linux top命令比较
    在这里插入图片描述

内存指标对比:
memTotal 对标 top中Mem total
memUsed对标 top中Mem used
memUsedRate对标 top中Mem used/total * 100
memFree对标 top中Mem free
memFreeRate对标 top中Mem free/total * 100
CPU指标对比:
cpuUseRate对标CPU中 us
cpuSysRate对标CPU中 sy
cpuIdleRate对标CPU中 id
cpuTotalRate = us + sy

另一种资源监控框架oshi介绍
  • OSHI.是一个基于JNA的免费的本地操作系统和Java的硬件信息库。它不需要安装任何额外的本机库
  • 通过API可以获取到:操作系统版本、进程、内存和CPU使用情况、磁盘和分区、设备、传感器等信息
  • https://github.com/oshi/oshi
OSHI使用
  1. pom.xml引入依赖包
<dependency>
    <groupId>com.github.oshi</groupId>
    <artifactId>oshi-core</artifactId>
    <version>6.0.0</version>
</dependency>
  1. demo
package nio.seq.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import oshi.hardware.*;
import oshi.software.os.NetworkParams;
import oshi.software.os.OSFileStore;
import oshi.software.os.OSProcess;
import oshi.software.os.OperatingSystem;
import oshi.hardware.CentralProcessor;
import oshi.util.FormatUtil;
import oshi.util.Util;
import oshi.SystemInfo;
import oshi.software.os.FileSystem;

import java.util.Arrays;
import java.util.List;

/**
 * oshi demo
 **/
public class SystemInfoTest {

    private static void printComputerSystem(final ComputerSystem computerSystem) {
        System.out.println("manufacturer: " + computerSystem.getManufacturer());
        System.out.println("model: " + computerSystem.getModel());
        System.out.println("serialnumber: " + computerSystem.getSerialNumber());
        final Firmware firmware = computerSystem.getFirmware();
        System.out.println("firmware:");
        System.out.println("  manufacturer: " + firmware.getManufacturer());
        System.out.println("  name: " + firmware.getName());
        System.out.println("  description: " + firmware.getDescription());
        System.out.println("  version: " + firmware.getVersion());
        System.out.println("  release date: " + (firmware.getReleaseDate() == null ? "unknown"
                : firmware.getReleaseDate() == null ? "unknown" : firmware.getReleaseDate()));
        final Baseboard baseboard = computerSystem.getBaseboard();

        System.out.println("baseboard:");
        System.out.println("  manufacturer: " + baseboard.getManufacturer());
        System.out.println("  model: " + baseboard.getModel());
        System.out.println("  version: " + baseboard.getVersion());
        System.out.println("  serialnumber: " + baseboard.getSerialNumber());
    }

    private static void printProcessor(CentralProcessor processor) {
        System.out.println(processor);
        System.out.println(" " + processor.getPhysicalProcessorCount() + " physical CPU(s)");
        System.out.println(" " + processor.getLogicalProcessorCount() + " logical CPU(s)");
        System.out.println("Identifier: " + processor.getProcessorIdentifier());
    }

    private static void printMemory(GlobalMemory memory) {
        System.out.println("以使用内存: " + FormatUtil.formatBytes(memory.getAvailable()) + "总共内存"
                + FormatUtil.formatBytes(memory.getTotal()));
        System.out.println("Swap used: " + FormatUtil.formatBytes(memory.getVirtualMemory().getSwapUsed()) + "/"
                + FormatUtil.formatBytes(memory.getVirtualMemory().getSwapTotal()));
    }

    private static void printCpu(CentralProcessor processor) {
        System.out.println("Uptime: " + FormatUtil.formatElapsedSecs(processor.getSystemCpuLoadTicks()[0]));
        long[] prevTicks = processor.getSystemCpuLoadTicks();
        System.out.println("CPU, IOWait, and IRQ ticks @ 0 sec:" + Arrays.toString(prevTicks));
        // Wait a second...
        Util.sleep(1000);
        long[] ticks = processor.getSystemCpuLoadTicks();
        System.out.println("CPU, IOWait, and IRQ ticks @ 1 sec:" + Arrays.toString(ticks));
        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
        long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
        long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal;
        long[] systemCpuLoadTicks = processor.getSystemCpuLoadTicks();
        System.out.format(
                "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%%n",
                100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu, 100d * idle / totalCpu,
                100d * iowait / totalCpu, 100d * irq / totalCpu, 100d * softirq / totalCpu, 100d * steal / totalCpu);
        System.out.format("CPU load: %.1f%% (counting ticks)%n", processor.getSystemCpuLoadBetweenTicks(systemCpuLoadTicks) * 100);
        System.out.format("CPU load: %.1f%% (OS MXBean)%n", processor.getSystemCpuLoadBetweenTicks(systemCpuLoadTicks) * 100);
        double[] loadAverage = processor.getSystemLoadAverage(3);
        System.out.println("CPU load averages:" + (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0]))
                + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1]))
                + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2])));
        // per core CPU
        StringBuilder procCpu = new StringBuilder("CPU load per processor:");
    }

    private static void printProcesses(OperatingSystem os, GlobalMemory memory) {
        System.out.println("Processes: " + os.getProcessCount() + ", Threads: " + os.getThreadCount());
        // Sort by highest CPU
        List<OSProcess> procs = os.getProcesses();
        System.out.println("   PID  %CPU %MEM       VSZ       RSS Name");
        for (int i = 0; i < procs.size() && i < 5; i++) {
            OSProcess p = procs.get(i);
            System.out.format(" %5d %5.1f %4.1f %9s %9s %s%n", p.getProcessID(),
                    100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime(),
                    100d * p.getResidentSetSize() / memory.getTotal(), FormatUtil.formatBytes(p.getVirtualSize()),
                    FormatUtil.formatBytes(p.getResidentSetSize()), p.getName());
        }
    }

    private static void printSensors(Sensors sensors) {
        System.out.println("Sensors:");
        System.out.format(" CPU Temperature: %.1f°C%n", sensors.getCpuTemperature());
        System.out.println(" Fan Speeds: " + Arrays.toString(sensors.getFanSpeeds()));
        System.out.format(" CPU Voltage: %.1fV%n", sensors.getCpuVoltage());
    }

    private static void printPowerSources(List<PowerSource> powerSources) {
        StringBuilder sb = new StringBuilder("Power: ");
        if (powerSources.size() == 0) {
            sb.append("Unknown");
        } else {
            double timeRemaining = powerSources.get(0).getTimeRemainingEstimated();
            if (timeRemaining < -1d) {
                sb.append("Charging");
            } else if (timeRemaining < 0d) {
                sb.append("Calculating time remaining");
            } else {
                sb.append(String.format("%d:%02d remaining", (int) (timeRemaining / 3600),
                        (int) (timeRemaining / 60) % 60));
            }
        }
        for (PowerSource pSource : powerSources) {
            sb.append(String.format("%n %s @ %.1f%%", pSource.getName(), pSource.getRemainingCapacityPercent() * 100d));
        }
        System.out.println(sb.toString());
    }

    private static void printDisks(List<HWDiskStore> diskStores) {
        System.out.println("Disks:");
        for (HWDiskStore disk : diskStores) {
            boolean readwrite = disk.getReads() > 0 || disk.getWrites() > 0;
            System.out.format(" %s: (model: %s - S/N: %s) size: %s, reads: %s (%s), writes: %s (%s), xfer: %s ms%n",
                    disk.getName(), disk.getModel(), disk.getSerial(),
                    disk.getSize() > 0 ? FormatUtil.formatBytesDecimal(disk.getSize()) : "?",
                    readwrite ? disk.getReads() : "?", readwrite ? FormatUtil.formatBytes(disk.getReadBytes()) : "?",
                    readwrite ? disk.getWrites() : "?", readwrite ? FormatUtil.formatBytes(disk.getWriteBytes()) : "?",
                    readwrite ? disk.getTransferTime() : "?");
            List<HWPartition> partitions = disk.getPartitions();
            if (partitions == null) {
                // TODO Remove when all OS's implemented
                continue;
            }
            for (HWPartition part : partitions) {
                System.out.format(" |-- %s: %s (%s) Maj:Min=%d:%d, size: %s%s%n", part.getIdentification(),
                        part.getName(), part.getType(), part.getMajor(), part.getMinor(),
                        FormatUtil.formatBytesDecimal(part.getSize()),
                        part.getMountPoint().isEmpty() ? "" : " @ " + part.getMountPoint());
            }
        }
    }

    private static void printFileSystem(FileSystem fileSystem) {
        System.out.println("File System:");
        System.out.format(" File Descriptors: %d/%d%n", fileSystem.getOpenFileDescriptors(),
                fileSystem.getMaxFileDescriptors());
        List<OSFileStore> fsArray = fileSystem.getFileStores();
        for (OSFileStore fs : fsArray) {
            long usable = fs.getUsableSpace();
            long total = fs.getTotalSpace();
            System.out.format(
                    " %s (%s) [%s] %s of %s free (%.1f%%) is %s "
                            + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]" : "%s")
                            + " and is mounted at %s%n",
                    fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(), fs.getType(),
                    FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()), 100d * usable / total,
                    fs.getVolume(), fs.getLogicalVolume(), fs.getMount());
        }
    }

    private static void printNetworkInterfaces(List<NetworkIF> networkIFs) {
        System.out.println("Network interfaces:");
        for (NetworkIF net : networkIFs) {
            System.out.format(" Name: %s (%s)%n", net.getName(), net.getDisplayName());
            System.out.format("   MAC Address: %s %n", net.getMacaddr());
            System.out.format("   MTU: %s, Speed: %s %n", net.getMTU(), FormatUtil.formatValue(net.getSpeed(), "bps"));
            System.out.format("   IPv4: %s %n", Arrays.toString(net.getIPv4addr()));
            System.out.format("   IPv6: %s %n", Arrays.toString(net.getIPv6addr()));
            boolean hasData = net.getBytesRecv() > 0 || net.getBytesSent() > 0 || net.getPacketsRecv() > 0
                    || net.getPacketsSent() > 0;
            System.out.format("   Traffic: received %s/%s%s; transmitted %s/%s%s %n",
                    hasData ? net.getPacketsRecv() + " packets" : "?",
                    hasData ? FormatUtil.formatBytes(net.getBytesRecv()) : "?",
                    hasData ? " (" + net.getInErrors() + " err)" : "",
                    hasData ? net.getPacketsSent() + " packets" : "?",
                    hasData ? FormatUtil.formatBytes(net.getBytesSent()) : "?",
                    hasData ? " (" + net.getOutErrors() + " err)" : "");
        }
    }

    private static void printNetworkParameters(NetworkParams networkParams) {
        System.out.println("Network parameters:");
        System.out.format(" Host name: %s%n", networkParams.getHostName());
        System.out.format(" Domain name: %s%n", networkParams.getDomainName());
        System.out.format(" DNS servers: %s%n", Arrays.toString(networkParams.getDnsServers()));
        System.out.format(" IPv4 Gateway: %s%n", networkParams.getIpv4DefaultGateway());
        System.out.format(" IPv6 Gateway: %s%n", networkParams.getIpv6DefaultGateway());
    }

    private static void printDisplays(List<Display> displays) {
        System.out.println("Displays:");
        int i = 0;
        for (Display display : displays) {
            System.out.println(" Display " + i + ":");
            System.out.println(display.toString());
            i++;
        }
    }

    private static void printUsbDevices(List<UsbDevice> usbDevices) {
        System.out.println("USB Devices:");
        for (UsbDevice usbDevice : usbDevices) {
            System.out.println(usbDevice.toString());
        }
    }

    public static void main(String[] args) {
        Logger LOG = LoggerFactory.getLogger(SystemInfoTest.class);
        LOG.info("Initializing System...");
        SystemInfo si = new SystemInfo();
        HardwareAbstractionLayer hal = si.getHardware();
        OperatingSystem os = si.getOperatingSystem();
        System.out.println("os platform===>" + os);
        LOG.info("Checking computer system...");
        printComputerSystem(hal.getComputerSystem());
        LOG.info("Checking Processor...");
        printProcessor(hal.getProcessor());
        LOG.info("Checking Memory...");
        printMemory(hal.getMemory());
        LOG.info("Checking CPU...");
        printCpu(hal.getProcessor());
        LOG.info("Checking Processes...");
        printProcesses(os, hal.getMemory());
        LOG.info("Checking Sensors...");
        printSensors(hal.getSensors());
        LOG.info("Checking Power sources...");
        printPowerSources(hal.getPowerSources());
        LOG.info("Checking Disks...");
        printDisks(hal.getDiskStores());
        LOG.info("Checking File System...");
        printFileSystem(os.getFileSystem());
        LOG.info("Checking Network interfaces...");
        printNetworkInterfaces(hal.getNetworkIFs());
        LOG.info("Checking Network parameterss...");
        printNetworkParameters(os.getNetworkParams());
        // hardware: displays
        LOG.info("Checking Displays...");
        printDisplays(hal.getDisplays());
        // hardware: USB devices
        LOG.info("Checking USB Devices...");
        printUsbDevices(hal.getUsbDevices(true));
    }

}

总结

Sigar 与OSHI都能够实现服务端资源监控,但是由于sigar是无法直接获取到操作系统资源,所以需要通过C语言的汇编文件获取,从而操作步骤会比较麻烦,相比于OSHI直接引入jar就可以获取硬件资源,若使用个人推荐OSHI

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一种用于构建独立的、可扩展的Java应用程序的开发框架。而SIGAR是一个Java库,用于获取系统信息,如CPU使用率、内存使用率、磁盘空间等。集成SIGAR可以让我们在Spring Boot应用程序中获取系统的详细信息。 要在Spring Boot中集成SIGAR,我们首先需要将SIGAR库添加到项目的依赖中。可以通过在pom.xml文件中添加如下依赖来实现: ```xml <dependency> <groupId>org.fusesource</groupId> <artifactId>sigar</artifactId> <version>1.6.4</version> <scope>runtime</scope> </dependency> ``` 添加依赖后,我们可以使用SIGAR库提供的API来获取系统信息。例如,我们可以编写一个来获取CPU使用率的示例: ```java import org.hyperic.sigar.CpuPerc; import org.hyperic.sigar.Sigar; import org.springframework.stereotype.Component; @Component public class SystemInfo { private Sigar sigar; public SystemInfo() { sigar = new Sigar(); } public double getCpuUsage() { try { CpuPerc cpuPerc = sigar.getCpuPerc(); return cpuPerc.getCombined(); } catch (Exception e) { e.printStackTrace(); return 0.0; } } } ``` 在这个示例中,我们使用了Sigar和CpuPerc来获取CPU使用率。通过调用getCpuPerc方法,我们可以得到一个CpuPerc对象,然后通过调用getCombined方法获取CPU的使用率。 在这个上添加@Component注解可以让Spring Boot自动扫描并将其作为一个Bean进行管理。我们可以在其他地方注入SystemInfo,并调用getCpuUsage方法来获取CPU使用率。 除了获取CPU使用率,SIGAR库还提供了很多其他的功能,如获取内存使用率、磁盘空间等。使用SIGAR库可以让我们更方便地在Spring Boot应用程序中获取系统信息,帮助我们监控和管理应用程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值