转载:https://blog.csdn.net/menghuanzhiming/article/details/77744267
任务需求:
关闭超时未支付的订单,将订单信息置为失效状态
相关技术:
quartz框架定时调度
1.在服务启动时,查询数据库中的已下单未支付的订单数据,按下单时间先后存入队列中,先下单的存到头不,后下单存入队列尾部,取队列的头元素
2.检测与现在的时间,如果超过40分钟,则执行数据操作,即关闭订单,但是只关闭未支付的订单,之后在将头元素从队列移出,并取出下一个元素进行检测,以此类推
3.如果检测出时间未到40分钟,则线程等待相应的时间差,之后在执行订单操作
相关问题:
1.在执行时要防止轮询任务追尾,即在上一个job未执行完毕时就开始下一次轮询,解决方法是在job上加@DisallowConcurrentExecution注解,该注解的作用是让下一次job要等待当前job执行完毕
2.设置的轮询间隔是35分钟一次,订单超时是40分钟,中间有5分钟的时间差,为了防止订单被多次加入队列中,在加入订单队列时要注意去重
相关代码
方式1:监听器 + quartz实现;
package com.taotao.order.scheduler;
import org.apache.log4j.Logger;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import com.taotao.order.controller.OrderController;
/**
*
* @ClassName: CancelOrderTask
* @Description: TODO(取消订单执行类)
* @author
* @date 2017年9月1日 上午10:58:26
*
*/
public class CancelOrderTask {
static Logger logger = Logger.getLogger(OrderController.class);
/*
* 1个job配置两个触发器
*/
public void cancelOrderTask() throws SchedulerException {
// 获得一个scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 创建一个job 任务名,任务组,任务执行类
JobDetail jobDetail = JobBuilder.newJob(CancelOrderJob.class)
.withIdentity("cancelOrderJob", "orderJob") //设定job的name和group
.storeDurably() //当job没有绑定触发器时放入scheduler不会抛出异常
.build();
scheduler.addJob(jobDetail, false);
//创建一个简单触发器,当启动项目时立即执行,CronTrigger的startNow()方法在设定定时任务后不起作用
Trigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity("firstCancelOrderTrigger", "firstOrderTrigger") //设定触发器name和group
.startNow() //设定立即执行
.forJob("cancelOrderJob", "orderJob") //触发器绑定job
.build();
//触发器加入到scheduler
scheduler.scheduleJob(simpleTrigger);
/*
* 创建一个CronTrigger触发器,设定触发时间
* CronTrigger触发器startNow没有任何作用,因为有自己的触发时间
*/
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("cancelOrderTrigger", "orderTrigger")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 * * * ?")) //触发时间(轮询时间)
.forJob("cancelOrderJob", "orderJob").build(); //触发器绑定job
// 将job和触发器绑定
scheduler.scheduleJob(cronTrigger);
String logInfo = jobDetail.getKey() + "取消订单任务定时器启动";
logger.info(logInfo);
System.out.println(logInfo);
scheduler.start();
}
public static void main(String[] args) {
CancelOrderTask cancelOrderTask = new CancelOrderTask();
try {
cancelOrderTask.cancelOrderTask();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
quartz任务的job,用于检测数据库失效订单并将其关闭,代码如下:
package com.nice.util.quartz;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import com.nice.dao.ValarmDataInfoMapper;
import com.nice.model.ValarmDataInfo;
import com.nice.service.AlarmDataService;
import org.apache.log4j.Logger;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import javax.annotation.Resource;
/**
* quartz任务的job,用于检测数据库失效订单并将其关闭
* @DisallowConcurrentExecution 这个注解标明上个任务没有执行完毕不会执行下个任务
*/
@DisallowConcurrentExecution
@Component
public class CancelOrderJob implements Job {
@Autowired
private AlarmDataService service;
//订单有效时间3分钟
public static final long EFFTIVE_TIME = 3 * 60 * 1000;
private Logger logger = Logger.getLogger(CancelOrderJob.class);
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
//下面这行代码必须加上,不然无法注入Bean,会报空指针错误
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
System.out.println("失效订单检测任务开始执行!");
Queue queue = new LinkedList<>();
// 在每次启动Job时去数据库查找失效订单,并加入到队列中(从数据库中查询,此处使用假数据)
List list = service.queryAll();
System.out.println("list大小:"+list.size());
if (!list.isEmpty()) {
for (ValarmDataInfo o : list) {
queue.offer(o);
}
}
// 获取队列的头元素,开始检测头订单是否失效
ValarmDataInfo element = queue.peek();
System.out.println("element="+element);
while (element != null) {
//时间差值
Long diff = this.checkOrder(element);
if (diff != null && diff >= EFFTIVE_TIME) {
System.out.println("开始关闭订单" + element.getDevicename() + "下单时间" + element.getAlarmstarttime());
// 弹出队列
queue.poll();
// 取下一个元素
element = queue.peek();
} else if (diff < EFFTIVE_TIME) {
try {
System.out.println("等待检测订单" + element.getDevicename() + "下单时间" + element.getAlarmstarttime() + "已下单"
+ diff / 1000 + "秒");
//线程等待
Thread.sleep(EFFTIVE_TIME - diff);
} catch (InterruptedException e) {
e.printStackTrace();
logger.info("CancelOrderJob.checkOrder定时任务出现问题");
}
}
}
}
/**
* 获取订单的下单时间和现在的时间差
*
* @author wangpeiqing 2016年4月16日
* @param order
* @return
*
*/
public Long checkOrder(ValarmDataInfo order) {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Long diff = null;
if (order != null) {
Date createTime = order.getAlarmstarttime();
try {
diff = sdf.parse(sdf.format(date)).getTime() - sdf.parse(sdf.format(createTime)).getTime();
} catch (ParseException e) {
e.printStackTrace();
}
}
// 返回值为毫秒
return diff;
}
}
监听器类
package com.taotao.order.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.quartz.SchedulerException;
import com.taotao.order.scheduler.CancelOrderTask;
public class CancelOrderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
CancelOrderTask task = new CancelOrderTask();
try {
task.cancelOrderTask();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
配置监听器:web.xml文件中添加监听器,启动程序时执行执行任务
com.taotao.order.listener.CancelOrderListener