CPU 内存 磁盘监控

---内存、CPU、磁盘空间监控
web.xml:
    <servlet>
        <servlet-name>MonitorServlet</servlet-name>
        <servlet-class>com.xxxxxx.aaa.monitor.servlet.MonitorServlet</servlet-class>
  </servlet>
com.xxxxxx.aaa.monitor.servlet.MonitorServlet:

package com.xxxxxx.aaa.monitor.servlet;

import com.xxxxxx.aaa.monitor.MonitorInit;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.apache.log4j.Logger;

public class MonitorServlet extends HttpServlet
{
  private static final long serialVersionUID = 6732821727026553573L;
  private static Logger logger = Logger.getLogger(MonitorServlet.class);

  public void init(ServletConfig config) throws ServletException
  {
    try
    {
      super.init(config);
      MonitorInit.init();
    }
    catch (Exception e)
    {
      logger.error(e.getMessage(), e);
    }
  }
}
com.xxxxxx.aaa.monitor.MonitorInit:

package com.xxxxxx.aaa.monitor;

import com.xxxxxx.aaa.monitor.task.QuartzManager;
import com.xxxxxx.dhm.aaa.commons.util.Utility;
import org.apache.log4j.Logger;

public class MonitorInit
{
  private static Logger logger = Logger.getLogger(MonitorInit.class);

  public static void main(String[] args) {
    init();
  }

  public static void init()
  {
    try
    {
      scheduleTask();
    }
    catch (Exception e)
    {
      logger.error("SDP Monitor init failed." + e.getMessage(), e);
    }
  }

  private static void scheduleTask()
    throws Exception
  {
    String quartzConfigFile = "monitor/quartz.properties";
    String quartzFullPath = Utility.getConfigFullPath(quartzConfigFile);

    String jobConfigFile = "monitor/quartz_job.xml";
    String jobFullPath = Utility.getConfigFullPath(jobConfigFile);

    QuartzManager quartzManager = new QuartzManager(quartzFullPath, jobFullPath);
    quartzManager.start();
  }
}

Utility:
package com.xxxxxx.dhm.aaa.commons.util;

import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;

public class Utility
{
  private static Logger logger = Logger.getLogger(Utility.class);

  public static final String OS_NAME = System.getProperty("os.name");

  public static boolean IS_WINDOWS = false;

  public static String getConfigFullPath(String configFileName)
  {
    String configFile = configFileName.replace('/', File.separator.charAt(0));
    if (!configFile.startsWith(File.separator))
    {
      configFile = File.separator + configFile;
    }
    return System.getProperty("user.dir") + configFile;
  }

  public static String getConfigFullPath(String configFileName, String systemName)
  {
    String configFile = configFileName.replace('/', File.separator.charAt(0));
    if (!configFile.startsWith(File.separator))
    {
      configFile = File.separator + configFile;
    }
    if (!systemName.equals(""))
    {
      systemName = (!systemName.startsWith(File.separator)) ? File.separator + systemName : systemName;
    }
    return System.getProperty("user.dir") + systemName + configFile;
  }

  public static Map<String, ?> transformHashMapToConcurrentHashMap(Map<String, ?> map)
  {
    Map cMap = new ConcurrentHashMap();
    Iterator iter = map.keySet().iterator();
    while (iter.hasNext())
    {
      String key = (String)iter.next();
      cMap.put(key, map.get(key));
    }
    return cMap;
  }

  public static String getLocalIp()
  {
    String localIp = null;
    try
    {
      localIp = InetAddress.getLocalHost().getHostAddress();
    }
    catch (UnknownHostException e)
    {
      logger.error(e.getMessage(), e);
    }
    return localIp;
  }

  static
  {
    if (!OS_NAME.toLowerCase().startsWith("windows"))
      return;
    IS_WINDOWS = true;
  }
}

QuartzManager:
package com.xxxxxx.aaa.monitor.task;

import java.io.FileInputStream;
import java.util.Properties;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzManager
{
  private static Scheduler sched = null;
  private String quartzConfigFile;
  private String quartzJobFile;

  public QuartzManager(String quartzConfigFile, String quartzJobFile)
  {
    this.quartzConfigFile = quartzConfigFile;
    this.quartzJobFile = quartzJobFile;
  }

  public void start() throws Exception
  {
    Properties props = new Properties();
    FileInputStream fis = new FileInputStream(this.quartzConfigFile);
    props.load(fis);
    props.put("org.quartz.plugin.jobInitializer.fileName", this.quartzJobFile);
    SchedulerFactory sf = new StdSchedulerFactory(props);
    sched = sf.getScheduler();
    sched.start();
    fis.close();
  }

  public void stop() throws Exception
  {
    sched.shutdown(false);
  }
}
监控CPU:
CpuMonitorTask:
package com.xxxxxx.aaa.monitor.task;

import com.xxxxxx.aaa.monitor.common.CommonConst;
import com.xxxxxx.dhm.aaa.commons.util.ShellExecuteUtil;
import com.xxxxxx.dhm.aaa.commons.util.Utility;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.StatefulJob;

public class CpuMonitorTask
  implements StatefulJob
{
  private Logger logger = Logger.getLogger(CpuMonitorTask.class);

  private static float lowCriticalValue = 75.0F;

  private static float highCriticalValue = 85.0F;

  private static int times = 5;

  private static int status = 0;

  private static boolean isNormal = true;

  private static float averageCpuRatio = -1.0F;

  public static int getStatus()
  {
    return status;
  }

  public static boolean isNormal()
  {
    return isNormal;
  }

  public CpuMonitorTask()
  {
    String monitor_cpuUsageThreshold = CommonConst.monitor_cpuUsageThreshold;
    lowCriticalValue = Float.valueOf(monitor_cpuUsageThreshold.split(":")[0]).floatValue();
    highCriticalValue = Float.valueOf(monitor_cpuUsageThreshold.split(":")[1]).floatValue();
    times = Integer.valueOf(monitor_cpuUsageThreshold.split(":")[2]).intValue();
  }

  public void execute(JobExecutionContext context)
  {
    try
    {
      if (!Utility.IS_WINDOWS)
      {
        pick();
      }
    }
    catch (Exception e)
    {
      this.logger.error(e.getMessage(), e);
    }
  }

  private void updateCpuStatus()
  {
    double rate = getAverageCpuRatio();

    if (this.logger.isDebugEnabled())
    {
      this.logger.debug("monitor the cpu usage ......CpuRatio=" + rate);
    }

    if (rate < lowCriticalValue)
    {
      isNormal = true;
    }

    if ((rate > lowCriticalValue) && (rate < highCriticalValue))
    {
      isNormal = true;

      this.logger.warn("The cpu status is warn!");
    }

    if (rate <= highCriticalValue)
      return;
    isNormal = false;
    this.logger.error("The cpu status is unnormal!");
  }

  private void pick()
    throws Exception
  {
    String shellPath = CommonConst.monitor_cpuUsageShellPath;

    String shellCmd = "sh " + shellPath + " " + (times + 1);

    String returnStr = ShellExecuteUtil.executeShellEcho(shellCmd);
    Float tmp = null;
    if ((null == returnStr) || ("".equals(returnStr)))
    {
      tmp = Float.valueOf(-1.0F);
    }
    else
    {
      tmp = Float.valueOf(returnStr);
    }

    if (tmp.intValue() == 0)
    {
      tmp = Float.valueOf(1.0F);
    }
    averageCpuRatio = tmp.floatValue();
    if (this.logger.isDebugEnabled())
    {
      this.logger.debug("pick finished!CpuRatio= " + averageCpuRatio);
    }

    updateCpuStatus();
  }

  public static float getAverageCpuRatio()
  {
    return averageCpuRatio;
  }
}

工具类:
ShellExecuteUtil
package com.xxxxxx.dhm.aaa.commons.util;

import java.io.IOException;
import java.io.InputStream;
import org.apache.log4j.Logger;

public class ShellExecuteUtil
{
  private static Logger logger = Logger.getLogger(ShellExecuteUtil.class);

  public static String executeShellEcho(String shellCmd) {
    StringBuffer buffer = new StringBuffer("");
    InputStream stream = null;
    try
    {
      Process process = Runtime.getRuntime().exec(shellCmd);
      stream = process.getInputStream();

      int a = stream.read();
      while (a != -1)
      {
        buffer.append((char)a);
        a = stream.read();
      }
      String str = buffer.toString().trim();

      return str;
    }
    catch (IOException e)
    {
      logger.error(e.getMessage(), e);
    }
    finally
    {
      if (null != stream)
      {
        try
        {
          stream.close();
        }
        catch (IOException e)
        {
          logger.error(e.getMessage(), e);
        }
      }
    }
    return "";
  }
}


常量:
package com.xxxxxx.aaa.monitor.common;

import com.xxxxxx.dhm.aaa.commons.util.Utility;

public class CommonConst
{
  public static final int STATUS_NORMAL = 0;
  public static final int STATUS_ALARM = 0;
  public static final int STATUS_FAILED = 0;
  public static final String monitor_memUsageThreshold = PropertiesUtil.getInstance().getProperties("monitor.memUsageThreshold");

  public static final String monitor_cpuUsageThreshold = PropertiesUtil.getInstance().getProperties("monitor.cpuUsageThreshold");

  public static final String monitor_diskUsageThreshold = PropertiesUtil.getInstance().getProperties("monitor.diskUsageThreshold");

  public static final String monitor_cpuUsageShellPath = Utility.getConfigFullPath("monitor/shell/getCpuUsage.sh");
  public static final String quartzConfigFile = "monitor/quartz.properties";
  public static final String jobConfigFile = "monitor/quartz_job.xml";
}

属性工具:
PropertiesUtil
package com.xxxxxx.aaa.monitor.common;

import com.xxxxxx.dhm.aaa.commons.util.Utility;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.log4j.Logger;

public class PropertiesUtil
{
  private static PropertiesUtil util;
  private static Logger logger = Logger.getLogger(PropertiesUtil.class);
  private Properties pro = new Properties();
  private Map<String, String> pmap = new HashMap();

  public PropertiesUtil()
  {
    try
    {
      File servicesFile = new File(Utility.getConfigFullPath("monitor/MonitorSystem.properties"));
      InputStream in = new FileInputStream(servicesFile);

      this.pro.load(in);
      Enumeration en = this.pro.propertyNames();

      while (en.hasMoreElements())
      {
        String key = (String)en.nextElement();
        String value = (String)this.pro.get(key);
        this.pmap.put(key, value);
      }

    }
    catch (IOException e)
    {
      logger.error(e.getMessage(), e);
    }
  }

  public static PropertiesUtil getInstance()
  {
    if (util == null)
    {
      util = new PropertiesUtil();
    }
    return util;
  }

  public String getProperties(String key)
  {
    if (!this.pmap.containsKey(key))
    {
      return "";
    }
    return ((String)this.pmap.get(key)).toString();
  }
}

磁盘监控:
DiskMonitorTask
package com.xxxxxx.aaa.monitor.task;

import com.xxxxxx.aaa.monitor.common.CommonConst;
import com.xxxxxx.dhm.aaa.commons.util.Utility;
import com.xxxxxx.dhm.aaa.commons.vo.DiskInfoEntity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.StatefulJob;

public class DiskMonitorTask
  implements StatefulJob
{
  private Logger logger = Logger.getLogger(DiskMonitorTask.class);
  private static float lowCriticalValue;
  private static float highCriticalValue;
  private static List<DiskInfoEntity> list = new ArrayList();

  public static List<DiskInfoEntity> getCurrentUsageList()
  {
    return list;
  }

  public static void setCurrentUsageList(List<DiskInfoEntity> list)
  {
    list = list;
  }

  public DiskMonitorTask()
  {
    String monitor_diskUsageThreshold = CommonConst.monitor_diskUsageThreshold;
    lowCriticalValue = Float.valueOf(monitor_diskUsageThreshold.split(":")[0]).floatValue();
    highCriticalValue = Float.valueOf(monitor_diskUsageThreshold.split(":")[1]).floatValue();
  }

  public void execute(JobExecutionContext context)
  {
    try
    {
      if (!Utility.IS_WINDOWS)
      {
        List usageList = getDiskUsage();

        for (int i = 0; i < usageList.size(); ++i)
        {
          DiskInfoEntity diskInfoEntity = (DiskInfoEntity)usageList.get(i);

          Float rate = Float.valueOf(diskInfoEntity.getUsage());

          if (rate.floatValue() < lowCriticalValue)
          {
            this.logger.debug(diskInfoEntity.toString());
          }

          if ((rate.floatValue() > lowCriticalValue) && (rate.floatValue() < highCriticalValue))
          {
            this.logger.warn("The current disk status is warn!" + diskInfoEntity.toString());
          }

          if (rate.floatValue() <= highCriticalValue)
            continue;
          this.logger.error("The current disk is unnormal!" + diskInfoEntity.toString());
        }

        setCurrentUsageList(usageList);
      }
    }
    catch (Exception e)
    {
      this.logger.error("pick finished!DiskMonitor = " + e.getMessage(), e);
    }
  }

  private List<DiskInfoEntity> getDiskUsage()
  {
    List list = new ArrayList();

    InputStream is = null;
    InputStreamReader isr = null;
    BufferedReader brStat = null;
    StringTokenizer tokenStat = null;
    try
    {
      Process process = Runtime.getRuntime().exec("df -mP");
      is = process.getInputStream();
      isr = new InputStreamReader(is);
      brStat = new BufferedReader(isr);

      brStat.readLine();

      String line = brStat.readLine();
      while ((null != line) && (line.trim().length() > 0))
      {
        System.out.println("each line:" + line);
        DiskInfoEntity diskInfoEntity = new DiskInfoEntity();

        tokenStat = new StringTokenizer(line);

        tokenStat.nextToken();
        tokenStat.nextToken();

        String used = tokenStat.nextToken().trim();
        diskInfoEntity.setUsedSpace(used);

        String available = tokenStat.nextToken().trim();
        diskInfoEntity.setAvailableSpace(available);

        String usage = tokenStat.nextToken().trim();
        diskInfoEntity.setUsage(usage.substring(0, usage.length() - 1));

        String dir = tokenStat.nextToken().trim();
        diskInfoEntity.setDir(dir);

        list.add(diskInfoEntity);
        line = brStat.readLine();
      }
    }
    catch (IOException ioe)
    {
      this.logger.error("getDiskUsage fail ! DiskMonitor = " + ioe.getMessage(), ioe);
    }
    catch (Exception e)
    {
      this.logger.error("getDiskUsage fail ! DiskMonitor = " + e.getMessage(), e);
    }
    finally
    {
      closeStream(is, isr, brStat);
    }
    return list;
  }

  private void closeStream(InputStream is, InputStreamReader isr, BufferedReader brStat)
  {
    try {
      if (null != brStat)
      {
        brStat.close();
      }
      if (null != isr)
      {
        isr.close();
      }
      if (null != is)
      {
        is.close();
      }
    }
    catch (IOException e)
    {
      this.logger.error(e.getMessage(), e);
    }
  }

  public static void main(String[] args)
  {
  }
}

磁盘实体类:
DiskInfoEntity
package com.xxxxxx.dhm.aaa.commons.vo;

import java.io.Serializable;

public class DiskInfoEntity
  implements Serializable
{
  private static final long serialVersionUID = 3529548649931127925L;
  private String dir;
  private String usedSpace;
  private String availableSpace;
  private String usage;

  public String getDir()
  {
    return this.dir;
  }

  public void setDir(String dir)
  {
    this.dir = dir;
  }

  public String getUsedSpace()
  {
    return this.usedSpace;
  }

  public void setUsedSpace(String usedSpace)
  {
    this.usedSpace = usedSpace;
  }

  public String getAvailableSpace()
  {
    return this.availableSpace;
  }

  public void setAvailableSpace(String availableSpace)
  {
    this.availableSpace = availableSpace;
  }

  public String getUsage()
  {
    return this.usage;
  }

  public void setUsage(String usage)
  {
    this.usage = usage;
  }

  public String toString()
  {
    return "dir:" + this.dir + ", usedSpace:" + this.usedSpace + ", availableSpace:" + this.availableSpace + ", usage:" + this.usage + ";";
  }
}

内存监控:
MemMonitorTask
package com.xxxxxx.aaa.monitor.task;

import com.xxxxxx.aaa.monitor.common.CommonConst;
import com.xxxxxx.dhm.aaa.commons.util.MemUtil;
import java.text.DecimalFormat;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.StatefulJob;

public class MemMonitorTask
  implements StatefulJob
{
  private Logger logger = Logger.getLogger(MemMonitorTask.class);
  private static float lowCriticalValue;
  private static float highCriticalValue;
  private static int status = 0;

  private static boolean isNormal = true;
  private static float currentUsage;

  public static int getStatus()
  {
    return status;
  }

  public static float getCurrentUsage()
  {
    return currentUsage;
  }

  public static void setCurrentUsage(float currentUsage)
  {
    currentUsage = currentUsage;
  }

  public static boolean isNormal()
  {
    return isNormal;
  }

  public MemMonitorTask()
  {
    String monitor_memUsageThreshold = CommonConst.monitor_memUsageThreshold;

    lowCriticalValue = Float.valueOf(monitor_memUsageThreshold.split(":")[0]).floatValue();
    highCriticalValue = Float.valueOf(monitor_memUsageThreshold.split(":")[1]).floatValue();
  }

  public void execute(JobExecutionContext context)
  {
    try
    {
      updateMemStatus();
    }
    catch (Exception e)
    {
      this.logger.error("MemMonitorTask.execute | fail! = " + e.getMessage(), e);
    }
  }

  public void updateMemStatus()
  {
    long total = MemUtil.getTotalMem();

    long used = MemUtil.getUsedMem();

    float ratio = (float)used / (float)total * 100.0F;
    DecimalFormat decimalFormat = new DecimalFormat("###.00");
    String formatRatio = decimalFormat.format(ratio) + "%";
    if (this.logger.isDebugEnabled())
    {
      this.logger.debug("MemMonitorTask.execute | formatRatio=" + formatRatio);
    }

    if (ratio < lowCriticalValue)
    {
      status = 0;
      isNormal = true;
    }

    if ((ratio > lowCriticalValue) && (ratio < highCriticalValue))
    {
      status = 1;
      isNormal = true;
      this.logger.warn("JVM free memory is warning! Usage ratio is:" + formatRatio);
    }

    if (ratio > highCriticalValue)
    {
      status = 2;
      isNormal = false;
      this.logger.error("JVM free memory is too low! Usage ratio is:" + formatRatio);
    }
    currentUsage = ratio;
  }
}

内存工具类:
package com.xxxxxx.dhm.aaa.commons.util;

public class MemUtil
{
  public static long getUsedMem()
  {
    long total = Runtime.getRuntime().maxMemory();
    long free = Runtime.getRuntime().freeMemory();
    return (total - free) / 1024L / 1024L;
  }

  public static long getTotalMem()
  {
    long total = Runtime.getRuntime().maxMemory();
    return total / 1024L / 1024L;
  }
}

getCpuUsage.sh:
#!/bin/sh

times=$1
cpuIdleInfo=`top -b -n $times | grep 'Cpu(s)' | awk -F' ' '{print $5}'`;

i=0;
total=0;
for eachInfo in $cpuIdleInfo
do
    i=`expr $i + 1`;
    
    #first rows not get
    if [ "X$i" == "X1" ]; then
        continue;
    fi;
    eachIdle=`echo $eachInfo | cut -d"%" -f1`;
    flag=`echo $eachInfo | cut -d"%" -f2`;
    if [ "X$flag" == "Xid," ]; then
        total=`echo $total + $eachIdle | bc`;
    fi;
    
done
ruleTimes=`expr $times - 1`;
totalIdle=`echo $total / $ruleTimes | bc`;
echo `echo 100 - $totalIdle | bc`;

MonitorSystem.properties:
monitor.memUsageThreshold=75:80
monitor.cpuUsageThreshold=75:85:5
monitor.diskUsageThreshold=75:85

quartz_job.xml:
<?xml version="1.0" encoding="UTF-8"?>
<quartz xmlns="http://www.opensymphony.com/quartz/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opensymphony.com/quartz/JobSchedulingData
  http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.xsd" version="1.5">
    <job>
        <job-detail>
            <name>MemMonitorTask</name>
            <group>quartz_job</group>
            <description>监控内存使用的任务</description>
            <job-class>com.xxxxxx.aaa.monitor.task.MemMonitorTask</job-class>
        </job-detail>
        <trigger>
            <simple>
                <name>MemMonitorTaskTrigger</name>
                <group>quartz_trigger</group>
                <description>每15秒执行一次</description>
                <job-name>MemMonitorTask</job-name>
                <job-group>quartz_job</job-group>
                <repeat-count>-1</repeat-count>
                <repeat-interval>15000</repeat-interval>
            </simple>
        </trigger>
    </job>
    <job>
        <job-detail>
            <name>CpuMonitorTask</name>
            <group>quartz_job</group>
            <description>监控CPU使用率的任务</description>
            <job-class>com.xxxxxx.aaa.monitor.task.CpuMonitorTask</job-class>
        </job-detail>
        <trigger>
            <simple>
                <name>CpuMonitorTaskTrigger</name>
                <group>quartz_trigger</group>
                <description>每分钟执行一次</description>
                <job-name>CpuMonitorTask</job-name>
                <job-group>quartz_job</job-group>
                <repeat-count>-1</repeat-count>
                <repeat-interval>60000</repeat-interval>
            </simple>
        </trigger>
    </job>
    <job>
        <job-detail>
            <name>DiskMonitorTask</name>
            <group>quartz_job</group>
            <description>监控当前系统磁盘使用的任务</description>
            <job-class>com.xxxxxx.aaa.monitor.task.DiskMonitorTask</job-class>
        </job-detail>
        <trigger>
            <simple>
                <name>DiskMonitorTaskTrigger</name>
                <group>quartz_trigger</group>
                <description>每分钟执行一次</description>
                <job-name>DiskMonitorTask</job-name>
                <job-group>quartz_job</job-group>
                <repeat-count>-1</repeat-count>
                <repeat-interval>60000</repeat-interval>
            </simple>
        </trigger>
    </job>
    
</quartz>


quartz.properties:

#============================================================================
# Configure Main Scheduler Properties
#============================================================================

org.quartz.scheduler.instanceName = SProxyScheduler
org.quartz.scheduler.instanceId = AUTO

#============================================================================
# Configure ThreadPool
#============================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 3
org.quartz.threadPool.makeThreadsDaemons = true
#============================================================================
# Configure Plugins
#============================================================================

org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.fileName = quartz_job.xml
org.quartz.plugin.jobInitializer.overWriteExistingJobs = false
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating = false
org.quartz.plugin.jobInitializer.validatingSchema = false

org.quartz.plugin.shutdownHook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownHook.cleanShutdown = true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值