三十七、【高级特性篇】定时任务:基于 APScheduler 实现测试计划的灵活调度

    • 前言
      • 准备工作
      • 第一部分:后端实现 - `APScheduler` 集成与任务调度
        • 1. 安装 `django-apscheduler`
        • 2. 配置 `django-apscheduler`
        • 3. 数据库迁移
        • 4. 创建调度触发函数
        • 5. 启动 APScheduler 调度器
        • 6. 创建定时任务管理的 API
        • 7. 后端初步测试
      • 第二部分:前端实现 - 定时任务管理界面
        • 1. 创建 API 服务 (`src/api/scheduler.ts`)
        • 2. 添加定时任务路由和侧边栏入口
        • 3. 实现定时任务列表页面 (`src/views/system/ScheduledJobListView.vue`)
        • 4. 实现定时任务创建/编辑对话框 (`src/views/system/ScheduledJobEditView.vue`)
      • 第三部分:后端 `ScheduledJobSerializer` 增强 (以支持回显触发器配置)
      • 第四部分:全面测试
    • 总结

前言

定时任务是自动化测试平台的核心功能之一,它允许我们设置测试计划在预定的时间或周期自动执行,从而实现无人值守的自动化回归测试、持续集成/部署后的冒烟测试等场景。
在这里插入图片描述
为什么选择 APSchedulerdjango-apscheduler

  • APScheduler (Advanced Python Scheduler): 一个轻量级且功能强大的 Python 任务调度库。它支持多种触发器(cron 模式、interval 模式、date 模式),非常灵活。
  • django-apscheduler: APScheduler 与 Django 的良好集成。它将 APScheduler 的调度信息(任务配置、下次运行时间等)直接存储在 Django 的数据库中,可以通过 Django ORM 来管理和查询定时任务,也方便通过 Django Admin 或自定义界面进行配置。
  • 与 Celery 配合: 如果定时任务本身是一个耗时操作(如执行一个包含大量用例的测试计划),直接在 APScheduler 的调度线程中执行会阻塞调度器,导致其他定时任务无法准时触发,甚至出现问题。所以,让 APScheduler 的定时任务只做一件轻量级的事情——将一个真正的耗时任务(即我们之前创建的 Celery 异步执行任务 execute_test_plan_task)提交到 Celery 任务队列中。 这样,调度器的稳定性不受测试执行时间长短的影响,同时又能利用 Celery 的异步、分布式处理能力。

准备工作

  1. Django 后端项目就绪: 确保 test-platform/backend 项目结构完整,Celery 和 Redis 已配置并运行。
  2. Vue3 前端项目就绪。
  3. Axios 和 API 服务已封装。
  4. Element Plus 集成完毕。

第一部分:后端实现 - APScheduler 集成与任务调度

1. 安装 django-apscheduler

在你的 Django 项目的虚拟环境中运行:

pip install django-apscheduler

在这里插入图片描述

2. 配置 django-apscheduler

打开 test-platform/backend/settings.py

a. 添加到 INSTALLED_APPS
在这里插入图片描述

# test-platform/backend/settings.py
# ...
INSTALLED_APPS = [# ... 其他应用 ...'django_apscheduler', # 添加这一行# ...
]
# ...
# APScheduler 配置
SCHEDULER_CONFIG = {"apscheduler.jobstores": {"default": {"class": "django_apscheduler.jobstores:DjangoJobStore"}},"apscheduler.executors": {"default": {"class": "apscheduler.executors.pool:ThreadPoolExecutor","max_workers": "20"}},"apscheduler.job_defaults": {"coalesce": False,"max_instances": 3},"apscheduler.timezone": TIME_ZONE
}# django_apscheduler 配置
APSCHEDULER_DATETIME_FORMAT = "N j, Y, f:s a"  # 默认时间格式
APSCHEDULER_RUN_NOW_TIMEOUT = 25  # 秒
# --- djangorestframework-simplejwt 设置 ---

b. 添加 APSCHEDULER 相关配置:
在这里插入图片描述

# test-platform/backend/settings.py
# ...
APSCHEDULER_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss" # 日期时间格式
APSCHEDULER_RUN_NOW_TIMEOUT = 25 # 立即运行任务的超时时间(秒)
3. 数据库迁移

运行 python manage.py migratedjango-apscheduler 会自动在数据库中创建管理调度任务所需的表。
在这里插入图片描述

4. 创建调度触发函数

这个函数是 APScheduler 将要调度的目标。它会接收测试计划ID,然后将真正的执行任务提交给 Celery。

a. api 目录下创建 scheduler_jobs.py 文件,填入以下代码:
在这里插入图片描述

# test-platform/api/scheduler_jobs.py
import logging
from django.utils import timezone
from api.models import TestPlan, TestRun # 导入 TestPlan 和 TestRun 模型
from api.tasks import execute_test_plan_task # 导入 Celery 任务
from api.utils.log_utils import record_operation_log # 导入操作日志工具logger = logging.getLogger(__name__)def trigger_test_plan_execution_job(test_plan_id: int):"""APScheduler 定时任务触发函数。此函数仅负责将测试计划执行任务提交到 Celery 异步队列。"""try:test_plan = TestPlan.objects.get(id=test_plan_id)# 1. 创建 TestRun 记录,初始状态为 PENDINGcurrent_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")initial_run_name = f"{test_plan.name} - (定时任务触发) {current_time}"test_run = TestRun.objects.create(test_plan=test_plan,name=initial_run_name,description=f"定时任务触发执行: {test_plan.name}",status='PENDING',total_cases=test_plan.test_cases.count() # 预估总数)# 2. 调用 Celery 任务异步执行task_result = execute_test_plan_task.delay(test_plan.id, str(test_run.id))# 3. 记录操作日志record_operation_log(user=None, # 由调度器触发,没有直接用户action_type='EXECUTE',target_resource='测试计划',target_id=test_plan.id,description=f"定时任务触发执行测试计划: '{test_plan.name}' (ID: {test_plan.id}), TestRun ID: {test_run.id}, Celery Task ID: {task_result.id}",details={"trigger_type": "scheduled", "test_run_id": str(test_run.id), "celery_task_id": task_result.id})logger.info(f"定时任务成功提交测试计划 (ID: {test_plan.id}) 执行到 Celery. TestRun ID: {test_run.id}, Celery Task ID: {task_result.id}")except TestPlan.DoesNotExist:logger.error(f"APScheduler 任务执行失败: 测试计划 (ID: {test_plan_id}) 未找到。")except Exception as e:logger.error(f"APScheduler 任务执行过程中发生未知错误 for plan ID {test_plan_id}: {e}", exc_info=True)
5. 启动 APScheduler 调度器

django-apscheduler 提供了两种启动调度器的方式:
a. 在 Django 应用启动时自动启动 (不推荐用于生产环境):在 apps.py 中,但会导致开发服务器重载时重复启动,并可能在多进程部署时引发问题。
b. 作为独立的 Management Command 启动:这是更健壮和推荐的方式,非常适合生产环境。

我们采用第二种方式

a. 创建 Management Command 文件:
test-platform/api/management/commands/ 目录下创建 runapscheduler.py 文件,填入以下代码:
在这里插入图片描述

# test-platform/api/management/commands/runapscheduler.py
import loggingfrom django.conf import settingsfrom apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
from django_apscheduler.jobstores import DjangoJobStore
from django_apscheduler.models import DjangoJobExecution
from django_apscheduler import utilfrom django.core.management.base import BaseCommand# 导入你的 APScheduler 定时任务函数
from api.scheduler_jobs import trigger_test_plan_execution_joblogger = logging.getLogger(__name__)# 定义清理旧作业执行记录的函数
@util.close_old_connections
def delete_old_job_executions(max_age=604_800):"""删除超过指定时间(默认7天)的旧作业执行记录。这个函数本身也可以被 APScheduler 定时调度。"""DjangoJobExecution.objects.delete_old_job_executions(max_age)class Command(BaseCommand):help = "Runs APScheduler."def handle(self, *args, **options):# 创建一个后台调度器实例scheduler = BackgroundScheduler(timezone=settings.TIME_ZONE)# 将 DjangoJobStore 添加到调度器scheduler.add_jobstore(DjangoJobStore(), "default")# 添加一个定时清理旧作业执行记录的任务 (可选)# 这个任务本身由 APScheduler 调度,每12小时执行一次scheduler.add_job(delete_old_job_executions,trigger=IntervalTrigger(hours=12),id="delete_old_job_executions",  # 指定一个 ID,方便管理max_instances=1, # 确保只有一个实例在运行replace_existing=True, # 如果已有同ID任务,则替换# misfire_grace_time=3600, # 如果任务错过,延迟1小时内仍可执行)logger.info("Added job 'delete_old_job_executions'.")# 打印当前所有已注册的 APScheduler 作业try:logger.info("Starting scheduler...")scheduler.start()except KeyboardInterrupt:logger.info("Stopping scheduler...")scheduler.shutdown()logger.info("Scheduler shut down successfully!")except Exception as e:logger.error(f"Scheduler startup failed: {e}", exc_info=True)scheduler.shutdown()

b. 运行调度器:
打开一个新的终端窗口 (除了 Django 开发服务器和 Celery Worker 的终端),激活虚拟环境,然后在 test-platform 目录下运行:

python manage.py runapscheduler

如果调度器成功启动,你会看到日志输出,并提示添加了 delete_old_job_executions 任务。
在这里插入图片描述

6. 创建定时任务管理的 API

我们将为 django-apschedulerDjangoJob 模型提供 RESTful API,以便前端进行管理。

a. api/serializers.py 中添加 ScheduledJobSerializer
在这里插入图片描述

# test-platform/api/serializers.py
# ...
from django_apscheduler.models import DjangoJob, DjangoJobExecution # 导入 APScheduler 的模型class ScheduledJobSerializer(serializers.ModelSerializer):test_plan_name = serializers.SerializerMethodField(read_only=True)job_type = serializers.CharField(source='job_func_name', read_only=True) # 方便前端显示函数名class Meta:model = DjangoJobfields = ['id', 'name', 'job_type', 'job_func_name', 'job_arguments', 'job_kwargs', 'job_state', 'next_run_time', 'start_date', 'end_date', 'max_instances', 'misfire_grace_time', 'coalesce', 'jobstore','test_plan_name' # 关联的测试计划名称]read_only_fields = ['job_type', 'job_func_name', 'job_state', 'next_run_time']extra_kwargs = {'job_arguments': {'required': False, 'allow_null': True}, # 允许 job_arguments 为空'job_kwargs': {'required': False, 'allow_null': True}, # 允许 job_kwargs 为空'job_state': {'required': False, 'read_only': True}}def get_test_plan_name(self, obj: DjangoJob):"""从 job_arguments 中解析 test_plan_id 并获取其名称"""if obj.job_arguments:try:# job_arguments 是一个元组的 JSON 字符串,例如 '["1"]'args_list = json.loads(obj.job_arguments)if args_list and isinstance(args_list, list) and len(args_list) > 0:test_plan_id = args_list[0]if isinstance(test_plan_id, (int, str)):try:test_plan = TestPlan.objects.get(id=int(test_plan_id))return test_plan.nameexcept TestPlan.DoesNotExist:return f"未知计划 (ID: {test_plan_id})"except (json.JSONDecodeError, IndexError, ValueError):passreturn "N/A"def create(self, validated_data: dict):# 在创建前,需要将 trigger_type 和 trigger_config 转换为 APScheduler 的 trigger 参数# 这部分逻辑通常在 ViewSet 的 create 方法中处理return super().create(validated_data)def update

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916039.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916039.shtml
英文地址,请注明出处:http://en.pswp.cn/news/916039.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ--消息顺序性

看本章之前强烈建议先去看博主的这篇博客 RabbitMQ--消费端单线程与多线程-CSDN博客 一、消息顺序性概念 消息顺序性是指消息在生产者发送的顺序和消费者接收处理的顺序保持一致。 二、RabbitMQ 顺序性保证机制 情况顺序保证情况备注单队列,单消费者消息严格按发送顺…

.net core接收对方传递的body体里的json并反序列化

1、首先我在通用程序里有一个可以接收对象型和数组型json串的反序列化方法public static async Task<Dictionary<string, string>> AllParameters(this HttpRequest request){Dictionary<string, string> parameters QueryParameters(request);request.Enab…

(10)机器学习小白入门 YOLOv:YOLOv8-cls 模型评估实操

YOLOv8-cls 模型评估实操 (1)机器学习小白入门YOLOv &#xff1a;从概念到实践 (2)机器学习小白入门 YOLOv&#xff1a;从模块优化到工程部署 (3)机器学习小白入门 YOLOv&#xff1a; 解锁图片分类新技能 (4)机器学习小白入门YOLOv &#xff1a;图片标注实操手册 (5)机器学习小…

Vue 脚手架基础特性

一、ref属性1.被用来给元素或子组件注册引用信息&#xff08;id的替代者&#xff09;2.应用在html标签上获取的是真实DOM元素&#xff0c;用在组件标签上是组件实例对象3.使用方式&#xff1a;(1).打标识&#xff1a;<h1 ref"xxx">...</h1> 或 <Schoo…

Ubuntu安装k8s集群入门实践-v1.31

准备3台虚拟机 在自己电脑上使用virtualbox 开了3台1核2G的Ubuntu虚拟机&#xff0c;你可以先安装好一台&#xff0c;安装第一台的时候配置临时调高到2核4G&#xff0c;安装速度会快很多&#xff0c;安装完通过如下命令关闭桌面&#xff0c;能够省内存占用&#xff0c;后面我们…

Word Press富文本控件的保存

新建富文本编辑器&#xff0c;并编写save方法如下&#xff1a; edit方法&#xff1a; export default function Edit({ attributes, setAttributes }) {return (<><div { ...useBlockProps() }><RichTexttagNameponChange{ (value) > setAttributes({ noteCo…

【编程趣味游戏】:基于分支循环语句的猜数字、关机程序

&#x1f31f;菜鸟主页&#xff1a;晨非辰的主页 &#x1f440;学习专栏&#xff1a;《C语言学习》 &#x1f4aa;学习阶段&#xff1a;C语言方向初学者 ⏳名言欣赏&#xff1a;"编程的核心是实践&#xff0c;而非空谈" 目录 1. 游戏1--猜数字 1.1 rand函数 1.2 sr…

UE5 UI 控件切换器

文章目录分类作用属性分类 面板 作用 可以根据索引切换要显示哪个子UI&#xff0c;可以拥有多个子物体&#xff0c;但是任何时间只能显示一个 属性 在这里指定要显示的UI的索引

scikit-learn 包

文章目录scikit-learn 包核心功能模块案例其他用法**常用功能详解****(1) 分类任务示例&#xff08;SVM&#xff09;****(2) 回归任务示例&#xff08;线性回归&#xff09;****(3) 聚类任务示例&#xff08;K-Means&#xff09;****(4) 特征工程&#xff08;PCA降维&#xff0…

Excel 将数据导入到SQLServer数据库

一般系统上线前期都会导入期初数据&#xff0c;业务人员一般要求你提供一个Excel模板&#xff0c;业务人员根据要求整理数据。SQLServer管理工具是支持批量导入数据的&#xff0c;所以我们可以使用该工具导入期初。Excel格式 第一行为字段1、连接登入的数据库并且选中你需要导入…

剪枝和N皇后在后端项目中的应用

剪枝算法&#xff08;Pruning Algorithm&#xff09; 生活比喻&#xff1a;就像修剪树枝一样&#xff0c;把那些明显不会结果的枝条提前剪掉&#xff0c;节省养分。 在后端项目中的应用场景&#xff1a; 搜索优化&#xff1a;在商品搜索中&#xff0c;如果某个分类下没有符合条…

cocos 2d游戏中多边形碰撞器会触发多次,怎么解决

子弹打到敌机 一发子弹击中&#xff0c;碰撞回调多次执行 我碰撞组件原本是多边形碰撞组件 PolygonCollider2D&#xff0c;我改成盒碰撞组件BoxCollider2D 就好了 用前端的节流方式。或者loading处理逻辑。我测试过了&#xff0c;是可以 本来就是多次啊,设计上貌似就是这样的…

Kubernetes环境中GPU分配异常问题深度分析与解决方案

Kubernetes环境中GPU分配异常问题深度分析与解决方案 一、问题背景与核心矛盾 在基于Kubernetes的DeepStream应用部署中&#xff0c;GPU资源的独占性分配是保障应用性能的关键。本文将围绕一个典型的GPU分配异常问题展开分析&#xff1a;多个请求GPU的容器本应独占各自的GPU&…

Django与模板

我叫补三补四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步今天来讲一讲视图Django与模板文件工作流程模板引擎&#xff1a;主要参与模板渲染的系统。内容源&#xff1a;输入的数据流。比较常见的有数据库、XML文件和用户请求这样的网络数据。模板&#xff1a…

日本上市IT企业|8月25日将在大连举办赴日it招聘会

株式会社GSD的核心战略伙伴贝斯株式会社&#xff0c;将于2025年8月25日在大连香格里拉大酒店商务会议室隆重举办赴日技术人才专场招聘会。本次招聘会面向全国范围内的优秀IT人才&#xff0c;旨在为贝斯株式会社东京本社长期发展招募优质的系统开发与管理人才。招聘计划&#xf…

低功耗设计双目协同画面实现光学变焦内带AI模型

低功耗设计延长续航&#xff0c;集成储能模块保障阴雨天气下的铁塔路线的安全一、智能感知与识别技术 多光谱融合监控结合可见光、红外热成像、激光补光等技术&#xff0c;实现全天候监测。例如&#xff0c;红外热成像可穿透雨雾监测山火隐患&#xff0c;激光补光技术则解决夜间…

datasophon下dolphinscheduler执行脚本出错

执行hive脚本出错&#xff1a; 错误消息&#xff1a; FAILED: RuntimeException Error loading hooks(hive.exec.post.hooks): java.lang.ClassNotFoundException: org.apache.atlas.hive.hook.HiveHookat java.net.URLClassLoader.findClass(URLClassLoader.java:387)at java.…

【Elasticsearch】安全地删除快照仓库、快照

《Elasticsearch 集群》系列&#xff0c;共包含以下文章&#xff1a; 1️⃣ 冷热集群架构2️⃣ 合适的锅炒合适的菜&#xff1a;性能与成本平衡原理公式解析3️⃣ ILM&#xff08;Index Lifecycle Management&#xff09;策略详解4️⃣ Elasticsearch 跨机房部署5️⃣ 快照与恢…

nodejs的npm

1. 什么是 npm&#xff1f; npm&#xff08;Node Package Manager&#xff09; 是 Node.js 的默认包管理工具&#xff0c;用于&#xff1a; 安装和管理依赖&#xff08;第三方库、框架等&#xff09;。运行项目脚本&#xff08;如启动服务、测试、构建等&#xff09;。发布和共…

外网访问内部私有局域网方案,解决运营商只分配内网IP不给公网IP问题

相信不少网友和我一样&#xff0c;为了实现远程控制、NAS访问、组建私有云、摄像头监控之类的需求&#xff0c;把光猫改成了桥接模式&#xff0c;并用自己的路由器拨号、进行端口了映射。本人之前一直用着没啥问题&#xff0c;不过&#xff0c;最近突然出现了无法访问的情况&am…