一个简单的定时任务框架

一个简单的定时任务框架

在生产中经常遇到一些问题,比如我有很多任务,每个任务都有固定的执行周期,比如一天一次,或者一小时一次,执行后我需要将执行结果以邮件的方式发给我。在可预期的将来,还会有这种任务出现,并且加入到这些任务的队列中。

显然,这些任务毫不相干,但都需要定时运行,由于我目前的任务都是python任务,一个一个的启动管理过于麻烦,而合并所有任务对于调试、后期添加任务都是一个负担。于是有了这个小框架,将其称为框架也算是过分的夸赞它了

这个框架有一个好处,那就是一次启动,随时添加、删除任务

主程序

主程序要干的就三件事

  1. 从配置文件中获取任务,并添加或删除任务
  2. 定点、定时执行任务,并且接收任务完成后的返回值
  3. 将返回值用邮件发送给特定的邮箱
    基于以上三个任务,需要用到这三个python库
apscheduler
watchdog
email

这几个库的具体用法可以自行百度,我这里只说说我用到的

监听

from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
class Timing(FileSystemEventHandler):
    def __init__(self):
        super(Timing, self).__init__()
        # 任务加载相关
        self._watch_path = "Task/task.json"
        
    def on_closed(self, event):
        pass

这里创建一个类,继承FileSystemEventHandler,这个类可以用来监听类属性self._watch_path规定的目录或文件,我这里监听的是一个文件,注意,这是类属性,别写错了。
父类FileSystemEventHandler中定义了几种方法对应监听到不同情况下的方法,如下

        self.on_any_event(event)
        {
            EVENT_TYPE_CREATED: self.on_created,
            EVENT_TYPE_DELETED: self.on_deleted,
            EVENT_TYPE_MODIFIED: self.on_modified,
            EVENT_TYPE_MOVED: self.on_moved,
            EVENT_TYPE_CLOSED: self.on_closed,
        }[event.event_type](event)

我这里只用到了on_closed,在文件修改后会触发一次,后续我们启动主方法后,就可以通过修改文件动态的加载任务,而不用反复启动停止主方法

动态加载任务

加载任务就需要用到apscheduler了,我愿称它为python最强定时任务

from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
        # 定时任务相关
        self.scheduler = BlockingScheduler()

通过self.scheduler就可以添加一个定时任务

self.scheduler.add_job(func1, 'cron', hour='0-23', minute="30")

这里表示0:30,1:30,2:30......23:30都会执行func1,如果需要每分钟执行一次可以设置为*/1
我们可以通过jobs = self.scheduler.get_jobs()获取当前的所有任务,返回值为一个列表,可通过如下方法获取方法的名称和ID

        for job in jobs:
			print(job.name)
			print(job.id)

任务的名称和代码中的方法名相同
获取到ID后就可以删除一个任务

self.scheduler.remove_job(jog_id)

我们通过读取配置文件,去添加或删除任务,将配置文件中新增的加入任务队列,消失的从队列中删除
以下代码仅供参考,可根据不同的任务去写不同结构的配置文件

{
  "PringA": {
    "Path": "Task/Sample_task.py",
    "Job": "PringA",
    "JobTime": {
      "hour": "0-23",
      "minute": "*/1"
    },
    "mail": {
    }
  },
    "PrintB": {
    "Path": "Task/Sample_task.py",
    "Job": "PrintB",
    "JobTime": {
      "hour": "0-23",
      "minute": "*/1"
    },
    "mail": {
    }
  }
}
    def load_task(self):
        if os.path.exists(self._watch_path):
            with open(self._watch_path, 'r', encoding='UTF-8') as f:
                load_dict = json.load(f)

            load_job_name = []
            for job in load_dict:
                job = load_dict[job]
                job_func = job['Job']
                job_name = job_func
                job_path = job['Path']
                job_time_hour = job['JobTime']['hour']
                job_time_minute = job['JobTime']['minute']
                load_job_name.append(job_name)

                if job_name not in self.job_dict.keys():
                    job_import = "from {} import {}".format(".".join(job_path.replace(".py", "").split("/")), job_func)
                    exec(job_import)
                    self.scheduler.add_job(eval(job_func), 'cron', hour=job_time_hour, minute=job_time_minute)
                    self.log.info("{} add job {}".format(job_import, job_func))

            for add_job_name in self.job_dict.keys():
                if add_job_name == "Timing.print_time":
                    continue
                if add_job_name not in load_job_name:
                    add_jog_id = self.job_dict[add_job_name]['id']
                    self.scheduler.remove_job(add_jog_id)
                    self.log.info("remove job {}, id={}".format(add_job_name, add_jog_id))

            self.update_job_dict()

            return load_dict
        else:
            self.log.info("{} not exists".format(self._watch_path))

接收返回值

当一个定时任务完成时,可以通过一个监听器去获取它的返回值

        self.scheduler.add_listener(函数, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)

在监听到任务完成返回一个值后,就会调用配置的函数去处理,在这里,我选择接收到返回值后就发送邮件

发送邮件

    def send_mail(self, event):
        job_name = self.job_dict_id[event.job_id]

        if self.task_dict is None:
            return
        if job_name not in self.task_dict.keys():
            return
        if 'mail' not in self.task_dict[job_name]:
            return

        mail_receivers = self.task_dict[job_name]['mail']['receivers'] if "receivers" in self.task_dict[job_name]['mail'] else self.mail_default_debug
        mail_debug = self.task_dict[job_name]['mail']['debug'] if "debug" in self.task_dict[job_name]['mail'] else self.mail_default_debug
        mail_msg = self.task_dict[job_name]['mail']['msg'] if "msg" in self.task_dict[job_name]['mail'] else self.mail_default_msg
        mail_subject = self.task_dict[job_name]['mail']['subject'] if "subject" in self.task_dict[job_name]['mail'] else self.mail_default_subject

        message = MIMEMultipart()
        message.attach(MIMEText(mail_msg, 'html', 'utf-8'))
        message['From'] = Header("Timing定时任务<{}>".format(self.mail_user), 'utf-8')  # 发送者
        message['To'] = Header("timing<timing@placeholder.com>", 'utf-8')  # 接收者

        job_return = event.retval

        send_success = False
        if job_return:
            file_path, file_len = job_return

            if os.path.exists(file_path):
                message['Subject'] = Header("{} {} 获取数据{}条".format(mail_subject, datetime.now().strftime(self.date_style), file_len), 'utf-8')
                msg_xlsx = MIMEApplication(open(file_path, 'rb').read())
                msg_xlsx.add_header('Content-Disposition', 'attachment', filename=os.path.basename(file_path))
                message.attach(msg_xlsx)

                try:
                    smtpObj = smtplib.SMTP_SSL(self.mail_host, self.mail_port)
                    smtpObj.login(self.mail_user, self.mail_pass)
                    smtpObj.sendmail(self.mail_sender, mail_receivers, message.as_string())
                    self.log.info("邮件发送成功 {} to {}".format(self.mail_sender, ", ".join(mail_receivers)))
                    smtpObj.quit()
                    send_success = True
                except smtplib.SMTPException as e:
                    self.log.error("无法发送邮件 {}".format(e))
                    message['Subject'] = Header("{} {} 发送邮件失败 {}".format(mail_subject, datetime.now().strftime(self.date_style), e), 'utf-8')
            else:
                message['Subject'] = Header("{} {} {}文件不存在".format(mail_subject, datetime.now().strftime(self.date_style), file_path), 'utf-8')
        else:
            message['Subject'] = Header("{} {} 任务无返回值".format(mail_subject, datetime.now().strftime(self.date_style)), 'utf-8')

        if not send_success:
            try:
                smtpObj = smtplib.SMTP_SSL(self.mail_host, self.mail_port)
                smtpObj.login(self.mail_user, self.mail_pass)
                smtpObj.sendmail(self.mail_sender, mail_debug, message.as_string())
                self.log.info("debug邮件发送成功 {} to {}".format(self.mail_sender, ", ".join(mail_receivers)))
                smtpObj.quit()
            except smtplib.SMTPException as e:
                self.log.error("debug邮件无法发送 {}".format(e))

    def run(self):
        self.scheduler.start()

添加链接描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值