初学SpringBoot之集成Quartz定时框架

目录

一、前言

二、开启Quartz之旅

2.1 Quartz框架介绍

2.2 从一个简单的demo开始

2.3Quartz框架中Job依赖注入service,实现数据库操作

2.4Quartz的API

三、感谢


一、前言

    这篇是学习SpringBoot的第七篇文章,这篇记录的是Quartz定时框架的使用。本次项目的工程还是延用这之前创建的firstproject工程。

    SpringBoot本身有一个定时任务的框架,但是Task也有不足之处。想动态的执行定时任务就比较困难。这就体现出了quartz的优势,它很容易的管理定时任务,很容易动态的操作定时任务。

    Quartz有三个核心概念:调度器、任务和触发器。三者关系是,调度器负责调度各个任务,到了某个时刻或者过了一定时间,触发器触动了,特定任务便启动执行。

    这边文章相对较长,主要分为4个部分,第一部分是介绍quartz框架,第二部分是使用框架做一个简单的例子,第三部分是能够进行数据库操作,第四部分是能够动态的操作任务的执行。便于大家寻找我i将每部分的标题飘上了橘黄色

二、开启Quartz之旅

2.1 Quartz框架介绍

Quartz框架有几大核心组件:
1.Scheduler任务调度
    是Quartz最核心的概念,需要把JobDetail和Trigger注册到scheduler中,才可以执行。
    使触发器及任务关联起来。(后面的代码中有详细的上下文)

scheduler.scheduleJob(jobDetail, cronTrigger);

2.Job任务,JobExecutionContext类提供调度上下文的各种信息
其实Job是接口,任务类要实现Job接口,重写Job接口的execute方法。
任务类还可以使用以下三个注解进行修饰:
@DisallowConcurrentExecution:此注解表示不允许这个Job并发执行

@PersistJobDataAfterExecution:此注解表示当这个Job的execute方法执行成功后,更新并存储它所持有的JobDetail属性中JobDataMap。如果使用这个注解,建议也使用@DisallowConcurrentExecution,因为并发执行过程中,JobDataMap有可能会发生冲突。

3.Trigger触发器/TruggerBuilder(创建Trigger)/ThreadPool(线程)
执行任务的规则;比如每天,每小时等。
一般情况使用SimpleTrigger,和CronTrigger,这些触发器实现了Trigger接口。SimpleScheduleBuilder和CronScheduleBuilder继承ScheduleBuilder抽象类 。

对于简单的时间来说,比如每天执行几次,使用SimpleTrigger。
对于复杂的时间表达式来说,比如每个月15日上午几点几分,使用CronTrigger以及CromExpression 类。

Priority:这个属性表示Trigger的权重。当两个Trigger触发时间相同时,权重大的那个先执行。Quartz默认的权重值为5。
Misfire Instruction:在Trigger接口中可以设置错过触发处理机制

4.JobDetail任务细节/JobBuilder(创建JobDetail)/JobStore保存Job数据:保存内存或数据库中)/JobDataMap:JSON数据格式
任务细节,Quartz执行Job时,需要新建个Job实例,但是不能直接操作Job类,所以通过JobDetail来获取Job的名称、描述信息。
调度器作为作业的总指挥,触发器作为作业的操作者,作业为应用的功能模块。
JobDetail有两个boolean属性。
isDurable:如果设为false,则对应的Job一旦没有关联的触发器,就会被Scheduler自动删除。 requestsRecovery:如果设为true,当Job执行中遇到硬中断(例如运行崩溃、机器断电等),Scheduler会重新执行。这种情况下,
JobExecutionContext.isRecovering()会返回ture。

5.Calendar(排除某个任务不执行)
一个Trigger可以和多个Calendar关联

6.监听器JobListener TriggerListener SchedulerListener

7.JobKey 和 TriggerKey
在Quartz中,可以分别通过JobKey和TriggerKey来唯一地识别一个Job或一个Trigger。
这两个Key都有两个关键属性:name和group。

2.2 从一个简单的demo开始

2.2.1 编写任务类,实现Job接口。我这里编写了两个任务类

    Job1

package com.xuexue.firstproject.Quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class QuartzJob1 implements Job {
    private void before(){
        System.out.println("QuartzJob1 任务开始执行");
    }

    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        before();
        System.out.println("开始:"+System.currentTimeMillis());
        // TODO 业务
        System.out.println("结束:"+System.currentTimeMillis());
        after();
    }

    private void after(){
        System.out.println("QuartzJob1 任务结束执行");
    }

}

Job2

package com.xuexue.firstproject.Quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


public class QuartzJob2 implements Job {

    private void before(){
        System.out.println("QuartzJob2任务开始执行");
    }

    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        before();
        System.out.println("开始:"+System.currentTimeMillis());
        // TODO 业务
        System.out.println("结束:"+System.currentTimeMillis());
        after();
    }

    private void after(){
        System.out.println("QuartzJob2任务结束执行");
    }
}

2.2.2编写任务调度和处理的类.

    Schedule提供了几个操作任务的方法

    scheduler.start():开启job

    scheduler.scheduleJob(jobDetail, cronTrigger):再容器中使触发器和任务关联

    scheduler.pauseAll():暂停所有任务

    scheduler.pauseJob(jobKey):暂停一个任务

    scheduler.resumeAll():恢复所有任务

    scheduler.resumeJob(jobKey):恢复一个任务

    scheduler.deleteJob(jobKey):从容器中删除一个任务

以下是任务调度和处理的类的代码:

package com.xuexue.firstproject.Quartz;

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import java.util.Date;

@Configuration
public class QuartzJobScheduler {
    // 任务调度
    @Autowired
    private Scheduler scheduler;

    /**
     * 开始执行所有任务
     */
    public void startJob() throws SchedulerException {
        startJob1(scheduler);
        startJob2(scheduler);
        scheduler.start();
    }

    /**
     * 将job1添加到容器中
     */
    private void startJob1(Scheduler scheduler) throws SchedulerException {
        // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
        // JobDetail 是具体Job实例
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob1.class).withIdentity("job1", "group1").build();
        // 基于表达式构建触发器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        // CronTrigger表达式触发器 继承于Trigger
        // TriggerBuilder 用于构建触发器实例
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job1", "group1")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }

    /**
     * 将job2添加到容器中
     */
    private void startJob2(Scheduler scheduler) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob2.class).withIdentity("job2", "group2").build();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0 0/1 * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job2", "group2")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }

    /**
     * 修改某个任务的执行时间
     */
    public boolean modifyJob(String name, String group, String time) throws SchedulerException {
        Date date = null;
        TriggerKey triggerKey = new TriggerKey(name, group);
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        String oldTime = cronTrigger.getCronExpression();
        if (!oldTime.equalsIgnoreCase(time)) {
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
                    .withSchedule(cronScheduleBuilder).build();
            date = scheduler.rescheduleJob(triggerKey, trigger);
        }
        return date != null;
    }

    /**
     *  获取job信息
     */
    public String getJobInfo(String name, String group) throws SchedulerException {

        TriggerKey triggerKey = new TriggerKey(name, group);
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        return String.format("time:%s,state:%s", cronTrigger.getCronExpression(),
                scheduler.getTriggerState(triggerKey).name());
    }

    /**
     * 暂停所有任务
     */
    public void pauseAllJob() throws SchedulerException {
        scheduler.pauseAll();
    }

    /**
     *暂停一个任务
     */
    public void pauseJob(String name, String group) throws SchedulerException {

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null)
            return;
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢复所有任务
     */
    public void resumeAllJob() throws SchedulerException {
        scheduler.resumeAll();
    }

    /**
     * 恢复某个任务
     */
    public void resumeJob(String name, String group) throws SchedulerException {
        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null)
            return;
        scheduler.resumeJob(jobKey);
    }

    /**
     *删除一个任务(从任务列表中删除)
     */
    public void deleteJob(String name, String group) throws SchedulerException {

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null){
            return;
        }
        scheduler.deleteJob(jobKey);
    }

    

}

2.2.3编写ApplicationStartQuartzJobListener类,采用监听spring容器加载完毕后事件,启动任务调用;并将Scheduler交给spring初始化管理。

package com.xuexue.firstproject.Quartz;

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;

@Configuration
public class ApplicationStartQuartzJobListener implements ApplicationListener<ContextRefreshedEvent>{
    @Autowired
    private QuartzJobScheduler quartzJobScheduler;


    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent)  {
        try {
            //启动任务
            quartzJobScheduler.startJob();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

    }

    /**
     * 初始注入scheduler
     */
    @Bean
    public Scheduler scheduler() throws SchedulerException{
        SchedulerFactory schedulerFactoryBean = new StdSchedulerFactory();
        return schedulerFactoryBean.getScheduler();
    }




}

2.2.4编译,启动,就会看到控制台输出定时任务打印的内容

    至此,一个简单的Quartz定时任务框架demo就结束了。接下来要再此基础上丰富一下,我们上面的例子并没有进行数据库操作,但是再实际开发中,在任务中进行数据库操作时必不可免的。下面我们进行数据库的操作。

2.3Quartz框架中Job依赖注入service,实现数据库操作

2.3.1我们再写一个任务类job3(ps:不用过多关注我的LxJobInfoService 里面都写了什么了,你可以自己随意写一个从数据库中取数据的方法,然后控制台输出一下,方便验证。我的这个方法时获取job的list集合,后面会有体现)

package com.xuexue.firstproject.Quartz;

import com.xuexue.firstproject.Services.imp.LxJobInfoService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;

public class QuartzJob3 implements Job {
    @Autowired
    private LxJobInfoService lxJobInfoService;
    @Override
    public void execute(JobExecutionContext jobExecutionContext)  {
            String cron= lxJobInfoService.getJobList().get(0).getJobCron();
            System.out.println("job3  "+cron);

    }
}

2.3.2将QuartzJobScheduler 类进行修改,添加startJob3()方法,并修改startJob()方法,修改后及新增后的方法如下;

    /**
     * 开始执行所有任务
     */
    public void startJob() throws SchedulerException {
        //startJob1(scheduler);  
        //startJob2(scheduler);
        startJob3(scheduler);
        scheduler.start();
    }

    /**
     *将job3添加到容器中
     */
    private void startJob3(Scheduler scheduler) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob3.class).withIdentity("job3", "group3").build();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job3", "group3")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }

2.3.3这样我们先试着执行一下,会发现,项目报 空指针异常。debug发现在任务中使用的service不能注入的,获取的对象为Null。

上网查找了一些资料,发现出现这个问题的原因:

    在quartz框架中,Job 是通过反射出来的实例,不受spring的管理,所以就导致注入失败。Scheduler现在交给Spring生成,在Spirng-context-support  jar包下org.springframework.scheduling.quartz包中有个SpringBeanJobFactory的类,job实例通过该类的createJobInstance方法创建。但是创建的job实例并没被spring管理,我们需要重写SpringBeanJobFactory类中的createJobInstance方法,将创建的job实例交给spring管理。接下来我们再写两个类来解决这个问题。

2.3.4定义MyJobFactory创建Quartz实例

package com.xuexue.firstproject.Quartz;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class MyJobFactory extends AdaptableJobFactory {
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        // 进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;

    }
}

2.3.5启动类注入MyJobFactory和Scheduler

package com.xuexue.firstproject;

import com.xuexue.firstproject.Quartz.MyJobFactory;
import lixue.ILiXue;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@SpringBootApplication
public class FirstprojectApplication {

    @Autowired
    private MyJobFactory myJobFactory;

    public static void main(String[] args) {
        SpringApplication.run(FirstprojectApplication.class, args);

    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        // 延时启动
        schedulerFactoryBean.setStartupDelay(20);
        // 自定义Job Factory,用于Spring注入
        schedulerFactoryBean.setJobFactory(myJobFactory);
        System.out.println("myJobFactory:"+myJobFactory);
        return schedulerFactoryBean;
    }

    @Bean
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }

}

2.3.6删除ApplicationStartQuartzJobListener类中注入Scheduler 的代码

    /**
     * 初始注入scheduler
     */
    @Bean
    public Scheduler scheduler() throws SchedulerException{
        SchedulerFactory schedulerFactoryBean = new StdSchedulerFactory();
        return schedulerFactoryBean.getScheduler();
    }

2.3.7编译,启动,这次就成功的注入进去了,控制台输出结果

    至此操作数据库的部分就结束了。Quartz定时任务的框架的优势就在于能够动态的管理我们的任务,那么接下来就写一个API管理我们的任务了

2.4Quartz的API

    这个API只是一个简单的例子,在操作上的还是会有一些漏洞。仅供参考,主要是体验一下如何动态的操作任务的,更加丰富完善的功能,还有待大家丰富。如有错误,请大家批评指正。

2.4.1设计数据库,储存任务列表,创建对应的实体类

package com.xuexue.firstproject.Beans;

import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;

import java.io.Serializable;
import java.util.Date;
@TableName("lx_job_info")
public class LxJobInfo extends Model<LxJobInfo> {
    /**
     * 任务id
     */
    @TableId(value = "job_id",type=IdType.AUTO)
    private Integer jobId;
    /**
     * 任务的jobkey 唯一地识别一个Job
     */
    private String jobName;
    /**
     * 任务的TriggerKey来唯一地识别一个Trigger
     */
    private String jobGroup;
    /**
     * 任务的所在类的全路径
     */
    private String jobClass;
    /**
     * 任务执行的cron表达式
     */
    private String jobCron;
    /**
     * 任务状态 0停止状态,1启动状态,2暂停状态
     */
        private Integer jobState;
    /**
     * 任务创建时间
     */
    private Date jobCreatetime;
    /**
     * 任务修改时间
     */
    private Date jobUpdatetime;

    public Integer getJobId() {
        return jobId;
    }

    public void setJobId(Integer jobId) {
        this.jobId = jobId;
    }

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public String getJobGroup() {
        return jobGroup;
    }

    public void setJobGroup(String jobGroup) {
        this.jobGroup = jobGroup;
    }

    public String getJobClass() {
        return jobClass;
    }

    public void setJobClass(String jobClass) {
        this.jobClass = jobClass;
    }

    public String getJobCron() {
        return jobCron;
    }

    public void setJobCron(String jobCron) {
        this.jobCron = jobCron;
    }

    public Integer getJobState() {
        return jobState;
    }

    public void setJobState(Integer jobState) {
        this.jobState = jobState;
    }

    public Date getJobCreatetime() {
        return jobCreatetime;
    }

    public void setJobCreatetime(Date jobCreatetime) {
        this.jobCreatetime = jobCreatetime;
    }

    public Date getJobUpdatetime() {
        return jobUpdatetime;
    }

    public void setJobUpdatetime(Date jobUpdatetime) {
        this.jobUpdatetime = jobUpdatetime;
    }

    @Override
    protected Serializable pkVal() {
        return null;
    }
}

2.4.2对应的service

package com.xuexue.firstproject.Services;

import com.baomidou.mybatisplus.service.IService;
import com.xuexue.firstproject.Beans.LxJobInfo;

import java.util.List;

public interface ILxJobInfoService extends IService<LxJobInfo> {
    //获取job的列表
    List<LxJobInfo> getJobList();
    //改变任务状态
    void updateJobState(LxJobInfo lxJobInfo);
}
package com.xuexue.firstproject.Services.imp;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.xuexue.firstproject.Beans.LxJobInfo;
import com.xuexue.firstproject.Daos.LxJobInfoMapper;
import com.xuexue.firstproject.Services.ILxJobInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class LxJobInfoService  extends ServiceImpl<LxJobInfoMapper,LxJobInfo> implements ILxJobInfoService{
    @Autowired
    LxJobInfoMapper lxJobInfoMapper;
    /**
     *查询任务列表
     */
    @Override
    public List<LxJobInfo> getJobList() {
        return lxJobInfoMapper.getJobList();
        //return lxJobInfoMapper.selectList(new EntityWrapper<>());
    }
    
    /**
     *修改任务状态
     */
    @Override
    public void updateJobState(LxJobInfo lxJobInfo) {
        lxJobInfoMapper.updateById(lxJobInfo);
    }

}

2.4.3对应的mapper

package com.xuexue.firstproject.Daos;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.xuexue.firstproject.Beans.LxJobInfo;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;
@Mapper
public interface LxJobInfoMapper extends BaseMapper<LxJobInfo> {
    //获取job的列表
    List<LxJobInfo> getJobList();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuexue.firstproject.Daos.LxJobInfoMapper">
	
	<resultMap id="LxJobInfo" type="com.xuexue.firstproject.Beans.LxJobInfo" >
		<result column="job_id" property="jobId" />
	    <result column="job_name" property="jobName" />
	    <result column="job_group" property="jobGroup" />
	    <result column="job_class" property="jobClass" />
		<result column="job_cron" property="jobCron" />
		<result column="job_state" property="jobState" />
		<result column="job_createtime" property="jobCreatetime" />
		<result column="job_updatetime" property="jobUpdatetime" />
	</resultMap>

	<sql id="Base_Column_List">
		t.job_id,
		t.job_name,
		t.job_group,
		t.job_class,
		t.job_cron,
		t.job_state,
		t.job_createtime,
		t.job_updatetime
	</sql>

	<select id="getJobList" resultMap="LxJobInfo">
		select t.job_id,
		t.job_name,
		t.job_group,
		t.job_class,
		t.job_cron,
		t.job_state,
		t.job_createtime,
		t.job_updatetime
		from lx_job_info t
	</select>

</mapper>

2.4.4对应的controller

package com.xuexue.firstproject.Controllers;


import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.xuexue.firstproject.Beans.LxJobInfo;
import com.xuexue.firstproject.Quartz.QuartzJobScheduler;
import com.xuexue.firstproject.Services.imp.LxJobInfoService;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/quartz")
public class QuartzApiController {

    @Autowired
    private QuartzJobScheduler quartzJobScheduler;

    @Autowired
    private LxJobInfoService lxJobInfoService;

    /**
     * @Author: lixue
     * @Description: 访问quartz.html页面
     * @Date: 2019/11/6 13:51
     * @param
     */
    @RequestMapping(value = "/toQuartz", method = RequestMethod.GET)
    public String toQuartz(){
        return "quartz/quartz.html";
    }

    /**
     * @Author: lixue
     * @Description: 查询任务列表
     * @Date: 2019/11/5 9:45
     * @param
     */
    @RequestMapping(value = "/getJobList", method = RequestMethod.POST)
    @ResponseBody
    public List<LxJobInfo> getJobList(){
        return lxJobInfoService.getJobList();
    }

    /**
     * @Author: lixue
     * @Description: 开启全部任务
     * @Date: 2019/11/6 14:02
     * @param
     */
    @RequestMapping(value = "/startAll", method = RequestMethod.POST)
    @ResponseBody
    public String startAll(){

        try {
        //获取任务列表
        List<LxJobInfo> list=lxJobInfoService.getJobList();
        //调用开启所有任务的方法
        quartzJobScheduler.startAllJob(list);
        //改变数据库中任务的状态为开启状态
         for(int i=0;i<list.size();i++){
             list.get(i).setJobState(1);
             lxJobInfoService.updateById(list.get(i));
         }

        //返回成功的字符串
        return "{\"result\":true}";

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    /**
     * @Author: lixue
     * @Description: 暂停全部任务
     * @Date: 2019/11/6 17:15
     * @param
     */
    @RequestMapping(value = "/suspendAll", method = RequestMethod.POST)
    @ResponseBody
    public String suspendAll(){

        try {
            //调用暂停所有任务的方法
            quartzJobScheduler.pauseAllJob();
            //改变数据库中任务的状态为暂停状态
            List<LxJobInfo> list=lxJobInfoService.getJobList();
            for(int i=0;i<list.size();i++){
                list.get(i).setJobState(2);
                lxJobInfoService.updateById(list.get(i));
                //lxJobInfoService.updateJobState(list.get(i));
            }
            //返回成功的字符串
            return "{\"result\":true}";

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @Author: lixue
     * @Description: 开启一个任务
     * @Date: 2019/11/6 17:14
     * @param jobId
     */
    @RequestMapping(value = "/startOne", method = RequestMethod.POST)
    @ResponseBody
    public String startOne(Integer jobId){

        try {

            LxJobInfo lxJobInfo=lxJobInfoService.selectById(jobId);
            //调用开启所有任务的方法

            quartzJobScheduler.startOneJob(lxJobInfo);
            //改变数据库中任务的状态为开启状态
            lxJobInfo.setJobState(1);
            lxJobInfoService.updateById(lxJobInfo);

            //返回成功的字符串
            return "{\"result\":true}";

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @Author: lixue
     * @Description: 暂停一个任务
     * @Date: 2019/11/6 17:14
     * @param jobId
     */
    @RequestMapping(value = "/suspendOne", method = RequestMethod.POST)
    @ResponseBody
    public String suspendOne(Integer jobId){

        try {

            LxJobInfo lxJobInfo=lxJobInfoService.selectById(jobId);
            //调用开启所有任务的方法
            quartzJobScheduler.pauseJob(lxJobInfo.getJobName(),lxJobInfo.getJobGroup());
            //改变数据库中任务的状态为暂停状态
            lxJobInfo.setJobState(2);
            lxJobInfoService.updateById(lxJobInfo);

            //返回成功的字符串
            return "{\"result\":true}";

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * @Author: lixue
     * @Description: 恢复一个任务
     * @Date: 2019/11/7 8:56
     * @param jobId
     */
    @RequestMapping(value = "/resumeOne", method = RequestMethod.POST)
    @ResponseBody
    public String resumeOne(Integer jobId){

        try {
            LxJobInfo lxJobInfo=lxJobInfoService.selectById(jobId);
            //调用开启所有任务的方法
            quartzJobScheduler.resumeJob(lxJobInfo.getJobName(),lxJobInfo.getJobGroup());
            //改变数据库中任务的状态为启动状态
            lxJobInfo.setJobState(1);
            lxJobInfoService.updateById(lxJobInfo);

            //返回成功的字符串
            return "{\"result\":true}";

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    @RequestMapping(value = "/addOneJob", method = RequestMethod.POST)
    @ResponseBody
    public String addOneJob(LxJobInfo lxJobInfo){
        lxJobInfoService.insert(lxJobInfo);
        return "{\"result\":true}";
    }


}

2.4.5对应的页面和js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    <div id="tablediv">
        <button id="startAll">开启全部任务</button>
        <button id="suspendAll">暂停全部任务</button>
        <table id="quartzTable"></table>
    </div>
    <from id="addJobFrom">
        name:<input type="text" id="jobName" name="jobName">
        group:<input type="text" id="jobGroup" name="jobName">
        类全路径:<input type="text" id="jobClass" name="jobName">
        cron:<input type="text" id="jobCron" name="jobName">
        <button id="addOneJob">添加任务</button>
    </from>
    <script type="text/javascript" src="../static/jquery.min.js"></script>
    <script type="text/javascript" src="../static/quartz/quartz.js"></script>
</body>

</html>
$(function(){
    getJobList();

    /**
     * 点击开启全部
     */
    $("#startAll").click(function(){
        //发送ajax请求
        $.ajax({
            url:"startAll",
            type:"post",
            dataType:"json",
            success:function(data){
                if (data){
                    alert("开启成功");
                }else{
                    alert("开启失败");
                }
                //刷新表格
                //$(".state").html("启动状态");
                getJobList();
            },
            error:function(err){
                console.log(err);
            }
        })

    });

    /**
     * 点击暂停全部
     */
    $("#suspendAll").click(function(){
        //发送ajax请求
        $.ajax({
            url:"suspendAll",
            type:"post",
            dataType:"json",
            success:function(data){
                if (data){
                    alert("暂停成功");
                }else{
                    alert("暂停失败");
                }
                //刷新表格
                //$(".state").html("暂停状态");
                getJobList();
            },
            error:function(err){
                console.log(err);
            }
        });

    });

    /**
     * 点击添加任务
     */


})
/**
 * 展示任务列表
 */
function getJobList(){
    $("#quartzTable").html("");
    $.ajax({
        url:"getJobList",
        type:"post",
        dataType:"json",
        success:function(data){
            //console.log(data);
            var str='<tr><th>序号</th><th>name</th><th>group</th><th>类全路径</th><th>cron</th><th>状态</th><th>操作</th></tr>';

            for (var i=0;i<data.length;i++){
                str+='<tr>';
                str+='<td>'+data[i].jobId+'</td>';
                str+='<td>'+data[i].jobName+'</td>';
                str+='<td>'+data[i].jobGroup+'</td>';
                str+='<td>'+data[i].jobClass+'</td>';
                str+='<td>'+data[i].jobCron+'</td>';

                var state="";
                if(data[i].jobState==0){
                    state="停止状态";
                }
                if(data[i].jobState==1){
                    state="启动状态";
                }
                if(data[i].jobState==2){
                    state="暂停状态";
                }

                str+='<td><button class="state">'+state+'</button></td>';
                str+='<td><button onclick="startOneJob('+data[i].jobId+')">开启</button>' +
                    '<button onclick="resumeOneJob('+data[i].jobId+')">恢复</button>' +
                    '<button onclick="suspendOneJob('+data[i].jobId+')">暂停</button>' +
                    '<button onclick="stopOneJob('+data[i].jobId+')">停止</button></td>';
                str+='</tr>';
            }
            $("#quartzTable").html(str);
        },
        error:function(err){
            console.log(err);
        }
    });
}

/**
 * 开启一个任务
 */
function startOneJob(jobId){
    $.ajax({
        url:"startOne",
        type:"post",
        data:{jobId:jobId},
        dataType:"json",
        success:function(data){
            if (data){
                alert("启动成功");
            }else{
                alert("启动失败");
            }
            //刷新表格
            //$(".state").html("暂停状态");
            getJobList();
        },
        error:function(err){
            console.log(err);
        }
    });
}

/**
 * 暂停一个任务
 */
function suspendOneJob(jobId){
    $.ajax({
        url:"suspendOne",
        type:"post",
        data:{jobId:jobId},
        dataType:"json",
        success:function(data){
            if (data){
                alert("暂停成功");
            }else{
                alert("暂停失败");
            }
            //刷新表格
            //$(".state").html("暂停状态");
            getJobList();
        },
        error:function(err){
            console.log(err);
        }
    });
}

/**
 * 恢复一个任务
 */
function resumeOneJob(jobId){
    $.ajax({
        url:"resumeOne",
        type:"post",
        data:{jobId:jobId},
        dataType:"json",
        success:function(data){
            if (data){
                alert("恢复成功");
            }else{
                alert("恢复失败");
            }
            //刷新表格
            //$(".state").html("暂停状态");
            getJobList();
        },
        error:function(err){
            console.log(err);
        }
    });
}

2.4.6修改一下我们前面的QuartzJobScheduler类

package com.xuexue.firstproject.Quartz;

import com.xuexue.firstproject.Beans.LxJobInfo;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import java.util.Date;
import java.util.List;

@Configuration
public class QuartzJobScheduler {
    // 任务调度
    @Autowired
    private Scheduler scheduler;


    /**
     * 开始执行所有任务
     */
    public void startJob() throws SchedulerException {
        //startJob1(scheduler);
        //startOneJob(scheduler,"job2","group2","0 0/1 * * * ?",QuartzJob2.class);
        //startJob2(scheduler);
        startJob3(scheduler);
        scheduler.start();
    }
    /**
     * 修改某个任务的执行时间
     *
     * @param name
     * @param group
     * @param time
     * @return
     * @throws SchedulerException
     */
    public boolean modifyJob(String name, String group, String time) throws SchedulerException {
        Date date = null;
        TriggerKey triggerKey = new TriggerKey(name, group);
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        String oldTime = cronTrigger.getCronExpression();
        if (!oldTime.equalsIgnoreCase(time)) {
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
                    .withSchedule(cronScheduleBuilder).build();
            date = scheduler.rescheduleJob(triggerKey, trigger);
        }
        return date != null;
    }

    /**
     * @Author: lixue
     * @Description: 获取job信息
     * @Date: 2019/11/4 14:38
     * @param name
     * @param group
     */
    public String getJobInfo(String name, String group) throws SchedulerException {

        TriggerKey triggerKey = new TriggerKey(name, group);
        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        return String.format("time:%s,state:%s", cronTrigger.getCronExpression(),
                scheduler.getTriggerState(triggerKey).name());
    }

    /**
     * @Author: lixue
     * @Description: 暂停所有任务
     * @Date: 2019/11/4 14:18
     * @param
     */
    public void pauseAllJob() throws SchedulerException {
        scheduler.pauseAll();
    }


    /**
     * @Author: lixue
     * @Description: 暂停一个任务
     * @Date: 2019/11/4 14:17
     * @param name
     * @param group
     */
    public void pauseJob(String name, String group) throws SchedulerException {

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null)
            return;
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢复所有任务
     *
     * @throws SchedulerException
     */
    public void resumeAllJob() throws SchedulerException {
        scheduler.resumeAll();
    }

    /**
     * 恢复某个任务
     *
     * @param name
     * @param group
     * @throws SchedulerException
     */
    public void resumeJob(String name, String group) throws SchedulerException {
        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null)
            return;
        scheduler.resumeJob(jobKey);
    }

    /**
     * @Author: lixue
     * @Description: 删除一个任务(从任务列表中删除)
     * @Date: 2019/10/30 17:29
     * @param name
     * @param group
     */
    public void deleteJob(String name, String group) throws SchedulerException {

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null){
            return;
        }
        scheduler.deleteJob(jobKey);
    }

    /**
     * @Author: lixue
     * @Description: 开启全部job(添加进任务列表,并启动)
     * @Date: 2019/10/28 10:38
     * @param lxJobInfoList job的list集合
     */
    public void startAllJob(List<LxJobInfo> lxJobInfoList)throws SchedulerException,ClassNotFoundException{

        for(int i=0;i<lxJobInfoList.size();i++){
            //反射机制
            Class jobClass = Class.forName(lxJobInfoList.get(i).getJobClass());
            // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
            // JobDetail 是具体Job实例
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(lxJobInfoList.get(i).getJobName(), lxJobInfoList.get(i).getJobGroup()).build();
            // 基于表达式构建触发器
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(lxJobInfoList.get(i).getJobCron());
            // CronTrigger表达式触发器 继承于Trigger
            // TriggerBuilder 用于构建触发器实例
            CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(lxJobInfoList.get(i).getJobName(), lxJobInfoList.get(i).getJobGroup())
                    .withSchedule(cronScheduleBuilder).build();
            //让调度器 使触发器和任务关联
            scheduler.scheduleJob(jobDetail, cronTrigger);
        }
        //启动任务调度器
        scheduler.start();
    }
    /**
     * @Author: lixue
     * @Description: 开启一个job(添加进任务列表,并启动)
     * @Date: 2019/10/28 15:44
     * @param lxJobInfo 一个job对象
     */
    public void startOneJob(LxJobInfo lxJobInfo) throws SchedulerException ,ClassNotFoundException{

        Class jobClass = Class.forName(lxJobInfo.getJobClass());
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(lxJobInfo.getJobName(), lxJobInfo.getJobGroup()).build();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(lxJobInfo.getJobCron());
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(lxJobInfo.getJobName(), lxJobInfo.getJobGroup())
                .withSchedule(cronScheduleBuilder).build();
        //让调度器 使触发器和任务关联
        scheduler.scheduleJob(jobDetail, cronTrigger);
        //启动任务调度器
        scheduler.start();
    }

    /*
    /**
     * @Author: lixue
     * @Description: 开启一个job  暂时不适用
     * @Date: 2019/10/26 14:15
     * @param scheduler 任务调度
     * @param name  jobkey 唯一地识别一个Job
     * @param group TriggerKey来唯一地识别一个Trigger
     * @param cron corn表达式
     * @param classs 任务的类
     */
    /*public void startOneJob(Scheduler scheduler, String name, String group, String cron, Class  classs) throws SchedulerException {
        //Class.forName("com.");
        JobDetail jobDetail = JobBuilder.newJob(classs).withIdentity(name, group).build();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(name, group)
                .withSchedule(cronScheduleBuilder).build();
        //让调度器 使触发器和任务关联
        scheduler.scheduleJob(jobDetail, cronTrigger);
        //启动任务调度器
        //scheduler.start();
    }*/
    private void startJob1(Scheduler scheduler) throws SchedulerException {
        // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
        // JobDetail 是具体Job实例
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob1.class).withIdentity("job1", "group1").build();
        // 基于表达式构建触发器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        // CronTrigger表达式触发器 继承于Trigger
        // TriggerBuilder 用于构建触发器实例
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job1", "group1")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }

    private void startJob2(Scheduler scheduler) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob2.class).withIdentity("job2", "group2").build();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0 0/1 * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job2", "group2")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }
    private void startJob3(Scheduler scheduler) throws SchedulerException {
        JobDetail jobDetail = JobBuilder.newJob(QuartzJob3.class).withIdentity("job3", "group3").build();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job3", "group3")
                .withSchedule(cronScheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }
}

2.4.7因为采用的是自动控制的,所以我们要将ApplicationStartQuartzJobListener类里面的内容注释掉,并让这个类不再实现ApplicationListener<ContextRefreshedEvent>

2.4.8编译,运行后出现如下页面

点击开启会开启一个任务,点击开启全部会开启全部任务,点击暂停会暂停任务,点击恢复会回复任务。

2.4.9项目中的瑕疵

这个小练习只是体验一下quartz框架的使用,在写的程序会有比较多的逻辑漏洞,还请大家谅解,更严谨完善的功能还需要大家自己根据实际情况进行完善以及修改。

1、开启全部任务没有筛选出已经开启的任务

2、关闭程序后再开启,状态显示的与实际情况不符

3、添加功能是没有实现的

三、感谢

在学习过程中,参考了很多的文章,如下几篇文章对我的帮助很大。

https://blog.csdn.net/u013042707/article/details/82934725

https://blog.csdn.net/upxiaofeng/article/details/79415108

https://blog.csdn.net/fly_captain/article/details/84257781

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,下面是关于使用Spring Boot和Quartz实现定时任务的简介: Quartz是一个流行的Java定时任务框架,可以在应用程序中执行各种定时任务,如定期备份、数据抽取和数据分析等。而Spring Boot是一个快速开发框架,提供了方便的集成Quartz的方式。 要使用Spring Boot和Quartz实现定时任务,首先需要在pom.xml文件中添加Quartz和Spring Boot Quartz的依赖项。然后,定义一个定时任务,可以使用注解@Scheduled来指定任务的执行时间。例如: ```java @Component public class MyScheduler { @Scheduled(cron = "0 0/10 * * * ?") public void myTask() { //执行定时任务的代码 } } ``` 上述代码表示每隔10分钟执行一次myTask()方法。然后,在Spring Boot的主类中,使用@EnableScheduling注解来开启定时任务的支持。例如: ```java @SpringBootApplication @EnableScheduling public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } } ``` 最后,启动应用程序,定时任务将按照预定时间自动执行。如果需要更高级的定时任务控制,还可以使用Quartz的其他功能,如JobDetail和Trigger等。 ### 回答2: SpringBoot是一个开源的Java开发框架,它提供了很多有用的工具和插件,其中之一便是定时任务quartzquartz是一款功能强大的开源调度框架,它提供了简单易用的任务调度机制,支持多线程和分布式部署,可以满足大部分的调度需求。 在SpringBoot中,我们可以很方便地使用quartz来实现定时任务。以下是使用SpringBootquartz实现定时任务的基本步骤: 1. 在pom.xml文件中添加quartz的依赖项: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` 2. 创建一个任务类,实现Job接口,并实现execute方法来定义任务逻辑: ``` @Component public class MyTask implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 定时任务的执行逻辑 } } ``` 3. 在配置文件中定义任务调度器,并为任务分配cron表达式: ``` spring: quartz: job-store-type: jdbc properties: org.quartz.scheduler.instanceName: MyScheduler org.quartz.threadPool.threadCount: 5 job-details: myJob: durability: true requestsRecovery: true trigger-details: myTrigger: cron: 0 0/1 * * * ? job-name: myJob job-data-map: name: world ``` 4. 在任务执行类中,使用@Scheduled注解来指定任务的执行时间: ``` @Component public class MyTask { @Scheduled(cron = "0 0/1 * * * ?") public void doTask(){ // 定时任务的执行逻辑 } } ``` 以上4种方法都是使用SpringBootquartz实现定时任务的简单示例,根据实际需求可以进行相应调整和扩展。总的来说,使用springbootquartz实现定时任务非常方便,能够让我们更加轻松地管理、调度和执行任务,提高开发效率和质量。 ### 回答3: Spring Boot是一款快速构建基于Spring应用的工具,可帮助简化开发和部署。在Spring Boot中,与Quartz相比,Scheduled定时任务通常是首选的。但是,Quartz比Scheduled更灵活、功能更强大、配置更灵活,因此在一些特殊情况下,使用Quartz进行定时任务调度是比较必要的。 Quartz是用Java语言编写的一个开源的调度框架,其主要用于在一个预定义的时间间隔内重复执行某个任务,或者在特定时间点执行某个任务。Quartz提供了众多功能,比如支持分布式定时任务管理、支持动态创建/删除任务、支持时间表达式、状态存储、监控和事件触发等等。 Spring Boot集成Quartz非常容易,只需要在Spring Boot项目中添加Quartz相关依赖,在Spring Boot配置文件中增加Quartz配置即可。以下是一个完整的Spring Boot集成Quartz的示例代码: 1.在pom.xml中添加Quartz和Spring Boot Starter Web依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> ``` 2.在配置文件中增加Quartz相关配置: ``` spring.quartz.job-store-type=memory spring.quartz.properties.org.quartz.threadPool.threadCount=10 spring.quartz.properties.org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore ``` 3.编写定时任务处理类: ``` @Component public class JobScheduler { @Autowired private Scheduler scheduler; @Scheduled(cron = "0 0/1 * * * ?") public void doSomething() { JobDetail jobDetail = buildJobDetail(); Trigger trigger = buildJobTrigger(jobDetail); try { scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { e.printStackTrace(); } } private JobDetail buildJobDetail() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("key", "value"); return JobBuilder.newJob(Job.class) .withIdentity(UUID.randomUUID().toString(), "job-group") .withDescription("sample job") .usingJobData(jobDataMap) .storeDurably(true) .build(); } private Trigger buildJobTrigger(JobDetail jobDetail) { return TriggerBuilder.newTrigger() .forJob(jobDetail) .withIdentity(UUID.randomUUID().toString(), "trigger-group") .withDescription("sample trigger") .withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 * * * ?")) .build(); } } ``` 虽然Quartz需要对定时任务进行配置,但是它提供了更为灵活的条件和任务调度,非常适合在实际生产环境中使用。总之,使用Spring Boot集成Quartz可以非常方便地实现定时任务的调度,既简单又强大,可以大大提高应用的效率和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值