JAVA(SSM项目)定时任务 Quartz 实现

本文介绍了如何在SSM(Spring、SpringMVC、Mybatis)框架下集成并管理Quartz定时任务。通过展示配置文件、数据库表结构以及关键代码,详细阐述了如何创建、修改、启动、暂停和删除定时任务,同时提供了本地测试示例,以帮助读者理解Quartz在实际项目中的应用。
摘要由CSDN通过智能技术生成

@TOCSSM项目动态实现定时任务管理

Quartz

在这里我的开发环境是idea2019+jdk1.8+mysql5.7

最近项目中的定时任务多了,就想了想,应该有管理功能来实现定时和发布

管理页面

  1. layui ,展示页面我用的是layui,很方便的;
  2. 后端使用的是Spring,Springmvc,Mybaties,就是 SSM 框架;
  3. 在这里说明一下,如果再执行代码方法里面调用service方法,有一个点很重要 不要创建构造函数 ,不然的话,定时任务启动会报错org.quartz.SchedulerException: Problem instantiating class ‘guilinsoft.ddsx.quartz.MCron’ [See nested exception: java.lang.IllegalAccessException: Class org.quartz.simpl.SimpleJobFactory can not access a member of class guilinsoft.ddsx.quartz.MCron with modifiers “”]

Caused by: java.lang.IllegalAccessException: Class org.quartz.simpl.SimpleJobFactory can not access a member of class guilinsoft.ddsx.quartz.MCron with modifiers “”;
4. 调用service接口方法需要Spring的工具类,用来获得配置文件中的bean

 public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }


    public static <T> T getBeanByClass(Class<T> c){
        return context.getBean(c);
    }
    public static Object getBeanByName(String name){
        return context.getBean(name);
    }
}
 [^1]: [mermaid语法说明](https://mermaidjs.github.io/)

Quartz pom.xml中

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz-jobs</artifactId>
  <version>2.3.0</version>
</dependency>

Quartz Spring-mvc.xml中

<bean id="instantiationTracingBeanPostProcessor" init-method="autoLoadTask" class="com.mlwy.controller.quartz.InstantiationTracingBeanPostProcessor">
</bean>

下面展示一些 内联代码片


 public class InstantiationTracingBeanPostProcessor  {
    private static final Logger log = Logger.getLogger(InstantiationTracingBeanPostProcessor.class);
	// 自己的定时任务管理Service,可自定义(需要改成自己的)
    QuartzService quartzService = SpringContextUtils.getBeanByClass(QuartzService.class);

    public QuartzService getQuartzService() {
        return quartzService;
    }

    public void setQuartzService(QuartzService quartzService) {
        this.quartzService = quartzService;
    }

    InstantiationTracingBeanPostProcessor(){}

    InstantiationTracingBeanPostProcessor(QuartzService quartzService){
        this.quartzService = quartzService;
    }


    /* spring加载完就执行该方法:init-method="autoLoadTask" */
    public void autoLoadTask() {
        //获取到所有需要启动的quartz集合
        log.info("【系统启动】所有定时任务开启...");
        List<QuartzVO> quartzList = quartzService.listQuartzByOn();
        if(Util.isEmpty(quartzList) == true){
            return;
        }
        for(int i=0;i<quartzList.size();i++){
            Class  clazz = null;
            try {
                clazz = Class.forName(quartzList.get(i).getClassname());
            }  catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            QuartzManager.addJob(quartzList.get(i).getJobname(), clazz, quartzList.get(i).getTriggercron());
        }

    }

}

Quartz mysql5.7 sql

DROP TABLE IF EXISTS bj_mlwy_quartz;

CREATE TABLE `bj_mlwy_quartz` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键(自增)',
  `jobgroup` VARCHAR(120)DEFAULT NULL COMMENT '任务组',
  `triggergroup` VARCHAR(120)DEFAULT NULL COMMENT '触发器组',  
  `jobname` VARCHAR(60)DEFAULT NULL COMMENT '任务名', 
  `triggername` VARCHAR(60)DEFAULT NULL COMMENT '触发器名',   
  `classname` VARCHAR(255)DEFAULT NULL COMMENT '执行代码的类名', 
  `enablestatus` INT(1) DEFAULT '1' COMMENT '是否禁用:0禁用;1启用',
  `triggercron` VARCHAR(60)DEFAULT NULL COMMENT '触发器类型(时间) */5 * * * * ?',
  `triggerstatus` INT(1)DEFAULT '0' COMMENT '任务状态:0关闭;1运行中;2暂停;',
  `createuser` VARCHAR(15) DEFAULT NULL COMMENT '创建人员名称',
  `createuserid` INT(11) DEFAULT NULL COMMENT '创建人员id',
  `createtime` VARCHAR(30) DEFAULT NULL COMMENT '创建时间',
  `updateuser` VARCHAR(15) DEFAULT NULL COMMENT '修改人员',
  `updateuserid` INT(11) DEFAULT NULL COMMENT '修改人员id',
  `updatetime` VARCHAR(30) DEFAULT NULL COMMENT '修改时间',
  `del` INT(1) DEFAULT NULL COMMENT '是否删除(1:可用)',
  `descript` VARCHAR(1024) COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='定时任务表'

Quartz Manager

package com.mlwy.controller.quartz;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.triggers.CronTriggerImpl;

public class QuartzManager {

    private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory();

    /**
     * 任务组名
     */
    private static String JOB_GROUP_NAME = "JOBGROUP_NAME";

    /**
     * 触发器组名
     */
    private static String TRIGGER_GROUP_NAME = "TRIGGERGROUP_NAME";

    /**
     * @Description: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
     * @param jobName
     *            任务名
     * @param cls
     *            任务
     * @param //参考quartz说明文档
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void addJob(String jobName, Class cls, String cron) {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            JobDetail job = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build();
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            // 按新的cronExpression表达式构建一个新的trigger
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, TRIGGER_GROUP_NAME)
                    .withSchedule(scheduleBuilder).build();

            // 交给scheduler去调度
            sched.scheduleJob(job, trigger);

            // 启动
            if (!sched.isShutdown()) {
                sched.start();
                System.err.println("添加任务:"+jobName);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 添加一个定时任务
     * @param jobName
     *            任务名
     * @param jobGroupName
     *            任务组名
     * @param triggerName
     *            触发器名
     * @param triggerGroupName
     *            触发器组名
     * @param jobClass
     *            任务
     * @param ,参考quartz说明文档
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName,
                              Class jobClass, String cron) {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            JobDetail job = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            // 按新的cronExpression表达式构建一个新的trigger
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName)
                    .withSchedule(scheduleBuilder).build();
            sched.scheduleJob(job, trigger);
            // 启动
            if (!sched.isShutdown()) {
                sched.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
     * @param jobName
     * @param
     */
    public static void modifyJobTime(String jobName, String cron) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);

        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
                // 按新的trigger重新设置job执行
                sched.rescheduleJob(triggerKey, trigger);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * @Description:修改任务,(可以修改任务名,任务类,触发时间)
     * 		原理:移除原来的任务,添加新的任务
     * @param oldJobName :原任务名
     * @param jobName
     * @param jobclass
     * @param cron
     * @date 2018年5月23日 上午9:13:10
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void modifyJob(String oldJobName, String jobName, Class jobclass, String cron) {
        /*
         * removeJob(oldJobName);
         * addJob(jobName, jobclass, cron);
         * System.err.println("修改任务"+oldJobName);
         */
        TriggerKey triggerKey = TriggerKey.triggerKey(oldJobName, TRIGGER_GROUP_NAME);
        JobKey jobKey = JobKey.jobKey(oldJobName, JOB_GROUP_NAME);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            Trigger trigger = (Trigger) sched.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            sched.pauseTrigger(triggerKey);// 停止触发器
            sched.unscheduleJob(triggerKey);// 移除触发器
            sched.deleteJob(jobKey);// 删除任务
            System.err.println("移除任务:" + oldJobName);

            JobDetail job = JobBuilder.newJob(jobclass).withIdentity(jobName, JOB_GROUP_NAME).build();
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            // 按新的cronExpression表达式构建一个新的trigger
            Trigger newTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, TRIGGER_GROUP_NAME)
                    .withSchedule(scheduleBuilder).build();

            // 交给scheduler去调度
            sched.scheduleJob(job, newTrigger);

            // 启动
            if (!sched.isShutdown()) {
                sched.start();
                System.err.println("添加新任务:" + jobName);
            }
            System.err.println("修改任务【" + oldJobName + "】为:" + jobName);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }


    /**
     * @Description: 修改一个任务的触发时间
     * @param triggerName
     * @param triggerGroupName
     * @param
     */
    public static void modifyJobTime(String triggerName, String triggerGroupName, String cron) {
        TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                // trigger已存在,则更新相应的定时设置
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
                // 按新的trigger重新设置job执行
                sched.resumeTrigger(triggerKey);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
     * @param jobName
     */
    public static void removeJob(String jobName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);
        JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            Trigger trigger = (Trigger) sched.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            sched.pauseTrigger(triggerKey);// 停止触发器
            sched.unscheduleJob(triggerKey);// 移除触发器
            sched.deleteJob(jobKey);// 删除任务
            System.err.println("移除任务:"+jobName);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 移除一个任务
     * @param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     */
    public static void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, triggerGroupName);
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.pauseTrigger(triggerKey);// 停止触发器
            sched.unscheduleJob(triggerKey);// 移除触发器
            sched.deleteJob(jobKey);// 删除任务
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description:暂停一个任务(使用默认组名)
     * @param jobName
     */
    public static void pauseJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:暂停一个任务
     * @param jobName
     * @param jobGroupName
     */
    public static void pauseJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:恢复一个任务(使用默认组名)
     * @param jobName
     */
    public static void resumeJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:恢复一个任务
     * @param jobName
     * @param jobGroupName
     * @date 2018年5月17日 上午9:56:09
     */
    public static void resumeJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description:启动所有定时任务
     */
    public static void startJobs() {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description 关闭所有定时任务
     */
    public static void shutdownJobs() {
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            if (!sched.isShutdown()) {
                sched.shutdown();
            }
		} catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。
     * @param jobName
     * @param
     * @date 2018年5月17日 上午10:03:26
     */
    public static void triggerJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.triggerJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。
     * @param jobName
     * @param jobGroupName
     * @date 2018年5月17日 上午10:03:26
     */
    public static void triggerJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            sched.triggerJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 获取任务状态
     * 		NONE: 不存在
     * 		NORMAL: 正常
     * 		PAUSED: 暂停
     * 		COMPLETE:完成
     * 		ERROR : 错误
     * 		BLOCKED : 阻塞
     * @param jobName 触发器名
     * @date 2018年5月21日 下午2:13:45
     */
    public static String getTriggerState(String jobName){
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);
        String name = null;
        try {
            Scheduler sched = gSchedulerFactory.getScheduler();
            TriggerState triggerState = sched.getTriggerState(triggerKey);
            name = triggerState.name();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return name;
    }

    /**
     * @Description:获取最近8次执行时间
     * @param cron
     * @date 2018年5月24日 下午5:13:03
     */
    public static List<String> getRecentTriggerTime(String cron) {
        List<String> list = new ArrayList<String>();
        try {
            CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
            cronTriggerImpl.setCronExpression(cron);
            // 这个是重点,一行代码搞定
            List<Date> dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 8);
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            for (Date date : dates) {
                list.add(dateFormat.format(date));
            }

        } catch (ParseException e) {
            e.printStackTrace();
        }
        return list;
    }

}

Quartz 启动

 	@RequestMapping("/able")
    @ResponseBody
    public AjaxJson able(HttpServletRequest request){
        AjaxJson json = new AjaxJson();
        try {
            String id = request.getParameter("id");
            QuartzVO vo = quartzService.selectByPrimaryKey(Integer.parseInt(id));
            // 启动
            vo.setTriggerstatus(1);
            // 编写启动程序
            Class  clazz = null;
            try {
                clazz = Class.forName(vo.getClassname());
            }  catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
         
            QuartzManager.addJob(vo.getJobname(), clazz, vo.getTriggercron());
 	//处理自己的业务逻辑
            String user = request.getSession().getAttribute("username").toString();
            Integer uid =  (Integer) request.getSession().getAttribute("userid");
            vo.setUpdatetime(Util.getNow(null));
            vo.setUpdateuser(user);
            vo.setUpdateuserid(uid);
            quartzService.updateByPrimaryKeySelective(vo);
            json.setSuccess(true);
            json.setMsg("启动成功");
        }catch (Exception e){
            json.setSuccess(false);
            json.setMsg(e.toString());
            e.printStackTrace();
        }
        return json;
    }


测试Job

public class MyJob implements Job {

    private static final Logger log = Logger.getLogger(MyJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        log.info("任务运行开始-------- start --------");
        try {
            String jobName = context.getJobDetail().getKey().getName();
            String jobGroup = context.getJobDetail().getKey().getGroup();
            String triggerName = context.getTrigger().getKey().getName();
            String triggerGroup = context.getTrigger().getKey().getGroup();

            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
            log.info("触发器Key:" + triggerName + ".." + triggerGroup + " 正在执行...");
            log.info("任务Key:" + jobName + ".." + jobGroup + " 正在执行,执行时间: "
                    + dateFormat.format(Calendar.getInstance().getTime()));

        }catch (Exception e) {
            log.info("捕获异常==="+e);
        }
        log.info("任务运行结束-------- end --------");
    }

}

本地测试

public class LoadTask {

    public static void main(String[] args) {
        System.err.println("【系统启动】");
        String corn = "0/5 * * * * ?";
        //QuartzManager.addJob("job1", "jobGooup", "trigger1", "triggerGroup", MyJob.class, corn);
        QuartzManager.addJob("job1",  MyJob.class, corn);
        System.err.println("添加任务一");
        QuartzManager.getTriggerState("jobs");

        //睡眠一分钟
        try {
            Thread.sleep(60L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        QuartzManager.modifyJobTime("job1", "0/3 * * * * ?");
        System.out.println("修改触发时间");

        try {
            Thread.sleep(15L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        QuartzManager.removeJob("job1");
        //QuartzManager.removeJob("job1", "jobGooup", "trigger1", "triggerGroup");
        System.out.println("删除任务");

        try {
            Thread.sleep(5L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("【添加定时任务】");
        QuartzManager.addJob("job1",  MyJob.class, corn);
        //QuartzManager.shutdownJobs();
        //System.out.println("停止所有任务");
        try {
            Thread.sleep(5L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("【暂停定时任务】");
        QuartzManager.pauseJob("job1");
        System.out.println("【立即运行一次】");
        QuartzManager.triggerJob("job1");
        try {
            Thread.sleep(5L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("【恢复定时任务】");
        QuartzManager.resumeJob("job1");
        try {
            Thread.sleep(5L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        QuartzManager.shutdownJobs();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuperChen12356

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值