使用最牛逼的任务调度工具-Quartz进行并发下载开发

Quartz介绍

Quartz 是一个完全由 Java 编写的开源作业调度框架,不要让作业调度这个术语吓着你,其实不难。尽管 Quartz 框架整合了许多额外功能,但就我们使用来说,你会发现它易用得简直让人受不了!
简单来说,任务调度就是在指定时间做指定的事,我们用Quartz执行任务必须的几个条件,一个是调度器,二是执行的任务,三是触发器(可以理解为设置闹钟)。连在一起就是我们使用调度器将任务和触发器绑定在一起执行,以达到在指定的时间点执行或循环执行任务。
接下来直接上代码,我是小白,所以代码会有点粗糙,如果有什么错误或者不当之处,请大家批评指正!

开发场景

公司之前交给我一个需求:根据不同的下载协议(SFTP,FTP,HTTPS),去下载道琼斯名单数据。要求一个协议绑定一个线程去下载,并且可以并发执行。

代码

//这是我绑定线程的方法
public static void bindThread(Constance c) throws Exception{
		// 定义一个 Schedule,用于绑定任务和触发器
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		//下面就是判断是哪些文件传输协议
		if (c.getProtocol().equalsIgnoreCase("sftp")) {
		
			JobDetail sftpJobDetail = JobBuilder.newJob(SFTPDownloadJob.class).withIdentity("job1", "group1").build();
			Trigger sftptrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow()
	                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();
			sftpJobDetail.getJobDataMap().put("data",c);
			scheduler.scheduleJob(sftpJobDetail, sftptrigger);
			scheduler.start();
			
		} else if (c.getProtocol().equalsIgnoreCase("ftp")) {
		
			JobDetail FTPJobDetail = JobBuilder.newJob(FTPDownloadJob.class).withIdentity("job2", "group2").build();
			Trigger FTPtrigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group2").startNow()
	                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();
			FTPJobDetail.getJobDataMap().put("data",c);
			scheduler.scheduleJob(FTPJobDetail, FTPtrigger);
			scheduler.start();
			
		}else if (c.getProtocol().equalsIgnoreCase("https")) {
		
			JobDetail HTTPSJobDetail = JobBuilder.newJob(HTTPSDownloadJob.class).withIdentity("job3", "group3").build();
			Trigger HTTPStrigger = TriggerBuilder.newTrigger().withIdentity("trigger3", "group3").startNow()
	                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();
			HTTPSJobDetail.getJobDataMap().put("data",c);
			scheduler.scheduleJob(HTTPSJobDetail, HTTPStrigger);
			scheduler.start();
			
		} else {
			throw new Exception("未知的第三方协议!");
		}
	}

接下来对上面的代码进行分析:

JobDetail sftpJobDetail = JobBuilder.newJob(SFTPDownloadJob.class).withIdentity("job1", "group1").build();

1、JobDetail,为什么需要它呢,都写在脸上了,工作详情嘛!我们执行任务需要知道任务的名称啊,分组之类的,这样打出来的 log 也好分辨。所以会在withIdentity中给它命名分组。
这个 JobDetail 是和哪个 Job 绑定的呢?我们还要知道 Class 信息,所以我们需要传入SFTPDownloadJob.class,根据不同的协议,创建不同的Job(这里下面会有说),然后把**Job.class传入到newJob中就可以啦。在任务执行的时候可能还需要传参,这些都会封装在 JobDetail 中。

Trigger sftptrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow()
	                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(0)).build();

2、同样的创建风格也用在触发器身上,想想看我们要设置一个闹钟,我们要设置开始执行的时间,执行的频率,执行的次数,这些一次性搞定,若是你英文还可以,看这些方法应该都明白了。
上面是设置一个简单的任务调度,如果我们想灵活的设置时间该怎么办呢?可以用Cron 表达式去编写你的规则,如:

CronTrigger cronTrigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity("trigger1", "group").startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")).build(); 

至于Cron 表达式怎么写,这里不多说,因为不需要自己写,我们可以用在线生成工具来生成他们。你只要百度搜索Cron 表达式在线生成器即可。当然,你如果对Cron表达式感兴趣或者你喜欢钻研,你可以去网上了解它的更多信息。

			sftpJobDetail.getJobDataMap().put("data",c);
			scheduler.scheduleJob(sftpJobDetail, sftptrigger);
			scheduler.start();

这三行代码,第一行是向Job(这个下面会说)里面put数据。第二、第三行是调度器绑定任务和触发器并开始执行。

到这里为止我们就已经搞定了最牛逼的定时任务了。然后下面我们来看一下我们Job的创建。下面我以SFTP为例,其他两种文件下载协议在这里其实也都是异曲同工。

//创建一个**Job需要实现Job接口
public class SFTPDownloadJob implements Job {
	//这是日志,忽略忽略
	private Logger log = null;
	//构造方法
	public SFTPDownloadJob(){
	}
	//这里就是必须要实现的方法了,我们在这里写逻辑
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobDataMap data = context.getJobDetail().getJobDataMap();
		Constance c = (Constance) data.get("data");
		PropertyConfigurator.configure(c.getLOG4J_CONFIGFILE_PATH());
		log = Logger.getLogger(FTPDownloadThread.class);
		log.debug("定时任务执行......");
		
		SFTPDownloadThread sftp = new SFTPDownloadThread(c);
		new Thread(sftp).start();
	}
}

继续分析、继续分析!

JobDataMap data = context.getJobDetail().getJobDataMap();
		Constance c = (Constance) data.get("data");

这两行代码主要是get从JobDetail put过来的Map数据,然后put过来时是什么类型接收就得是什么类型。

PropertyConfigurator.configure(c.getLOG4J_CONFIGFILE_PATH());
		log = Logger.getLogger(FTPDownloadThread.class);
		log.debug("定时任务执行......");

这里是写日志嗷,不用管不用管。๑乛◡乛๑

SFTPDownloadThread sftp = new SFTPDownloadThread(c);
		new Thread(sftp).start();

这里是自己写的文件下载线程,把它放在Job类中启动是因为要给它设置定时规则。

然后这里重要介绍的就是Quartz,至于这个文件下载线程我下面把代码放出来,就不做详细解释了,嗷对了,使用Quartz之前记得将依赖导入项目哦。

public class SFTPDownloadThread implements Runnable {
	private Logger log = null;
	private Constance c = null;
	
	public SFTPDownloadThread(Constance constance) {
		PropertyConfigurator.configure(constance.getLOG4J_CONFIGFILE_PATH());
		log = Logger.getLogger(FTPDownloadThread.class);
		this.c = constance;
		 
	}
	/**
	 * 根据文件名称校验文件是否在目录中存在
	 * 当文件存在且可读时返回true
	 * @param fileName
	 * @param saveDir
	 * @return Boolean
	 */
	private boolean fileNameCheck(String fileName, String saveDir) {
		File dir = new File(saveDir);
		if (!dir.exists()) {
			return false;
		}
		File file = new File(dir, fileName);
		if (file.exists() && file.canRead()) {
			return true;
		}
		return false;
	}
	/**
	 * 根据配置获取本地存储文件名称
	 * 目前配置支持的表达式:{DATE}==>yyyyMMddHHmmss;{FILENAME}==>远程文件原始名称
	 * @param remoteFileName
	 * @param config
	 * @return
	 */
	private String getSaveFileName(String remoteFileName) {
		if (StringUtils.isBlank(c.getSaveFileNameFormat())) {
			return remoteFileName;
		}
		String saveFileName = c.getSaveFileNameFormat();
		saveFileName = saveFileName.replace("{FILENAME}", remoteFileName.substring(0, remoteFileName.lastIndexOf(".")));
		saveFileName = saveFileName.replace("{DATE}", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
		return saveFileName;
	}
	@SuppressWarnings("unchecked")
	@Override
	public void run() {
		Channel channel = null;
		ChannelSftp chSftp = null;
		Session session = null;
		try {
		
			JSch jsch = new JSch();
			session = jsch.getSession(c.getFTP_USERID(), c.getHost(), c.getFTP_PORT());
			session.setPassword(c.getFTP_PASSWD());
			session.setTimeout(c.getFTP_TIMEOUT());
			Properties config = new Properties();
			config.put("StrictHostKeyChecking", "no");
			session.setConfig(config);
			session.connect();

			channel = session.openChannel("sftp");
			channel.connect();	
			chSftp = (ChannelSftp) channel;
			// 获取文件列表
			Vector<LsEntry> entrys = chSftp.ls(c.getFTP_REMOTEIR());
			// 切换工作目录
			if (StringUtils.isNotEmpty(c.getFTP_REMOTEIR())) {
				chSftp.cd(c.getFTP_REMOTEIR());
			}
			for (Iterator<LsEntry> it = entrys.iterator(); it.hasNext();) {
				LsEntry entry = it.next();
				// 抛弃目录
				if (entry.getAttrs().isDir()) {
					continue;
				}
				// 抛弃链接
				if (entry.getAttrs().isLink()) {
					continue;
				}
				String remoteFileName = entry.getFilename();
				// 文件名称过滤
				if (StringUtils.equals(FILTERTYPE_FILENAME, c.getFileFilterType())) {
					if (!StringUtils.equals(remoteFileName, c.getFilterStr())) {
						log.info("根据文件名称过滤文件:"+ remoteFileName);
						continue;
					}
				} else if (StringUtils.equals(FILTERTYPE_REGEX, c.getFileFilterType())) {
					if (!remoteFileName.matches(c.getFilterStr())) {
						log.info("根据文件名称正则过滤文件:"+remoteFileName);
						continue;
					}
				}
				// 文件验重
				if (StringUtils.equalsIgnoreCase(CHECKTYPE_FILENAME, c.getCheckType())) {
					if (fileNameCheck(remoteFileName, c.getFTP_LOCALDIR())) {
						log.info("根据文件名过滤文件:"+ remoteFileName);
						continue;
					}
				} else if (StringUtils.equals(CHECKTYPE_CHANGEDATE, c.getCheckType())) {
					log.debug("时间字符串......"+MyMethods.fileRead(c.getFTP_TYPE()));
					String mTime = String.valueOf(entry.getAttrs().getMTime());
					if (StringUtils.equals(mTime, MyMethods.fileRead(c.getFTP_TYPE()))) {
						log.info("根据文件更新时间过滤文件:"+ remoteFileName);
						continue;
					}
					//写入本地checkStr
					MyMethods.fileWrite(c.getFTP_TYPE(), mTime);
					log.debug("时间字符串2......"+MyMethods.fileRead(c.getFTP_TYPE()));
				} 

				// 获取存储文件名称
				String saveFileName = getSaveFileName(remoteFileName);
				
				boolean downloadStatus = false;
				// 下载
				OutputStream os = null;
				try {
					log.debug(String.format("==========开始下载文件:【%s】【%s】==========", remoteFileName, saveFileName));
					os = new FileOutputStream(new File(c.getFTP_LOCALDIR(), saveFileName));
					chSftp.get(remoteFileName, os);
					// 下载完成后创建OK标识文件
					File okFile = new File(c.getFTP_LOCALDIR(), saveFileName + ".OK");
					if (!okFile.exists()) {
						okFile.createNewFile();
					}
					// 更新下载状态及校验串
					//utils.saveConfigStatus(config);
					downloadStatus = true;
					log.debug(String.format("==========文件下载成功:【%s】【%s】==========", remoteFileName, saveFileName));
				} catch (SftpException e) {
					throw e;
				} finally {
					if (null != os) {
						os.close();
					}
					// 下载逻辑未成功时,删除临时文件及相应的标识文件
					if (!downloadStatus) {
						File dataFile = new File(c.getFTP_LOCALDIR(), saveFileName);
						if (dataFile.exists()) {
							dataFile.delete();
						}
						
						File okFile = new File(c.getFTP_LOCALDIR(), saveFileName + ".OK");
						if (okFile.exists()) {
							okFile.deleteOnExit();
						}
					}
				}
			}
		}catch(Exception e){
			log.error("数据文件下载异常", e);
			e.printStackTrace();
		}finally{
			
			if (null != chSftp) {
				chSftp.quit();
			}
			
			if (null != channel) {
				channel.disconnect();
			}

			if (null != session) {
				session.disconnect();
			}
			
		}
	}
}

好了,到这我的分享就结束了。然后可能因为水平有限会被大佬们发现一些错误,请大家批评指正!如果有觉得这篇文章能帮到你或者感觉还不错的麻烦点个小赞!感谢感谢。

如有侵权,联系立删!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值