这个需求的大约背景如下:
客户会在新建项目的时候选择项目的上下架时间,精确到秒,如果设置的是未上架,需要在过了上架时间之后将项目上架;同理,需要在下架时间过了之后将项目状态改为下架。不过系统里的项目不多,总数也就几百的样子,同时在发行中的项目数量更少。
实现思路(包括过程)
1. 首先考虑到的是spring的定时任务,每秒钟去扫一次库,如果有需要上下架的,做上下架的处理。
然后立刻马上就觉得这种实现方案不现实,这样系统会崩。
2. 经过沟通制定了第二种方案,基于切面的定时任务,在每次查询项目的时候判断这批次查出来的项目里有没有下架的,有的话启动一个线程,执行变更状态的操作。
这种方案的问题在于不占用系统的资源,但是数据很不准确,而且得到查询的结果之后可能会有后续的操作,后续要改代码的地方太多,而且会带来很多新的bug,不太可取
3. 得到一个折中方案:设置一个监听,把需要的项目上下架的时间都取到,每秒钟执行一次,判断当前时间有没有需要处理的上下架请求,有的话处理,没有的话等待下次执行。
实现如下:
1. 建立一个任务,继承TimerTask,在启动工程的时候加一个监听
ProjectTaskListener projectTaskListener = (ProjectTaskListener) context.getBean("compProjectTaskListener"); Timer timer = new Timer(); timer.schedule(projectTaskListener, 1000, 1000);
2. 任务实现如下的功能
※ 初始化数据 :将已经上下架的数据处理掉;将需要处理的数据放入到队列中
public void init() { //处理数据 marketingProjectMapper.updateProjectUp(); marketingProjectMapper.updateProjectDown(); ProjectTaskDealVO projectTaskDeal = ProjectTaskDealVO.getInstance(); //初始化数据 List<ProjectTaskVO> projectTaskList = marketingProjectMapper.queryPreDealList(); if(projectTaskList != null && projectTaskList.size() > 0) { System.out.println("have data"); projectTaskDeal.setProjectTasks(projectTaskList); projectTaskDeal.setNextDealTime(projectTaskList.get(0).getDealTime()); } }
※逻辑处理: 判断当前时间和下个需要处理的时间,如果时间到了,根据业务类型处理数据,并将需要处理的时间列表中最早的时间移除,将时间队列中第一条数据的处理时间赋值给下次处理时间;如果未到时间,不做任何操作
/** * 逻辑处理 */ public void deal() { ProjectTaskDealVO projectTaskDeal = ProjectTaskDealVO.getInstance(); Long now = System.currentTimeMillis(); if(projectTaskDeal.getProjectTasks().size() > 0) { if(now >= projectTaskDeal.getNextDealTime()) { ProjectTaskVO projectTask = projectTaskDeal.getProjectTasks().get(0); if(projectTask.getOperate() == 1) { marketingProjectMapper.updateProjectUp(); } else { marketingProjectMapper.updateProjectDown(); } projectTaskDeal.getProjectTasks().remove(0); if(projectTaskDeal.getProjectTasks().size() > 0) { projectTaskDeal.setNextDealTime(projectTaskDeal.getProjectTasks().get(0).getDealTime()); } else { projectTaskDeal.setNextDealTime(0L); } } } }
※执行方法: 继承父类需要实现的方法,加一个初始化次数的参数,只有在项目启动的时候执行一次
@Override public void run() { if(firstRun == 0) { init(); firstRun++; } deal(); }
3 业务数据的维护
从上面的代码中可以看出,业务的对象用到了单例模式实现,这样保证系统运行中只有一个实例存在,不论是在任务里还是在业务里取到的都是同一个实例
※ 单例模式实现代码如下: 私有化构造方法;静态的getInstance()方法:如果实例存在,返回实例,如果不存在,创建实例,所以在创建一次之后就一直存在了
private static ProjectTaskDealVO instance; private ProjectTaskDealVO() { } /** * 创建实体类 * @return */ public static synchronized ProjectTaskDealVO getInstance() { if(instance == null) { instance = new ProjectTaskDealVO(); } return instance; }
※业务中维护如下
/** * 构想: 项目数量比较少,上下架修改时间操作也不会频繁,项目上下架有两种操作 1 新增 2 修改 队列中原有的数据不做处理 * 可能队列中需要处理的时间要比实际上要处理的时间多一些,尽量保证数据有效性 * @param dealTime 处理时间 * @param operate 上架 下架 排队到期 * @param */ private void addTimeToTask(long dealTime, int operate) { ProjectTaskDealVO projectTaskDeal = ProjectTaskDealVO.getInstance(); ProjectTaskVO newTask = new ProjectTaskVO(); newTask.setDealNum(1); newTask.setDealTime(dealTime); newTask.setOperate(operate); if(projectTaskDeal.getProjectTasks().size() > 0) { for(int i = 0; i < projectTaskDeal.getProjectTasks().size(); i++) { ProjectTaskVO compTask = projectTaskDeal.getProjectTasks().get(i); if(compTask.getDealTime().longValue() == newTask.getDealTime().longValue()) { if(compTask.getOperate() == newTask.getOperate()) { compTask.setDealNum(compTask.getDealNum() + 1); projectTaskDeal.getProjectTasks().set(i, compTask); break; } } else if(compTask.getDealTime().longValue() >= newTask.getDealTime().longValue()) { newTask.setDealNum(1); projectTaskDeal.getProjectTasks().add(i, newTask); } } if(projectTaskDeal.getNextDealTime() > dealTime) { projectTaskDeal.setNextDealTime(dealTime); } } else { projectTaskDeal.setNextDealTime(dealTime); projectTaskDeal.getProjectTasks().add(newTask); } }
在项目开发的过程中走过的一些误区:
※因为平时做的都是单线程的开发,对于一些定义特别不明确,开始考虑的是时间数据也在任务中维护,为了保证在业务中的调用,就把任务设置成了单例模式,然后这个就和spring的配置不兼容(spring是后来加载的),这样的设计确实也违反了类的单一性原则,然后就考虑把业务数据取出来单独维护,定时任务通过spring维护
※在timer和多线程中纠结了很久,最后还是选择了timer。。。。