django-simpletask5
本项目由 opencode + deepseek-v4-flash 生成
一个轻量级的 Django 异步任务执行框架,提供声明式的任务模型、信号驱动的自动发布、Worker 进程异步执行,以及内置的 Cron 定时调度。
特性
- 声明式任务模型 — 继承
Task模型即可定义任务,自动处理创建/更新/删除事件 - 信号驱动 — Django 信号自动拦截模型变更,发布
TaskExecution到消息队列 - 自定义事件 — 通过
task.trigger('event_name')触发任意事件 - 灵活的执行器映射 — 不同事件可绑定不同的执行器类
- 队列路由 — 不同事件可路由到不同优先级队列
- 重试与超时 — 失败自动重试(指数退避),支持 Pickup 超时检测;运行中孤儿任务由执行锁回收
- 拒绝回退 — 执行器可通过
RejectException拒绝当前任务,消息自动重新入队等待下次调度 - 加密字段 — 敏感数据自动加密存储
- Cron 调度 — 内置 crontab 守护进程,支持代码注册与数据库覆盖
- 归档统计 — 已完成执行记录自动归档为加密 JSONL,并生成日统计
- Worker 注册中心 — 基于 Redis 的 Worker 心跳与状态追踪
- Django Admin 集成 — 完整的后台管理界面
依赖
- Python >= 3.10
- Django >= 4.2(兼容至 5.2.x)
- Kombu >= 5.6.2(消息队列,支持 RabbitMQ/Redis/内存)
- Redis >= 7.4.0(分布式锁、Worker 注册中心)
- 详见
pyproject.toml
安装
pip install django-simpletask5
django-simpletask5 使用 django-app-requires 自动管理依赖的 app(如 django_safe_fields),无需手动添加到 INSTALLED_APPS。只需在 settings.py 中调用一次 patch:
from django_app_requires import patch_all as django_app_requires_patch_all
django_app_requires_patch_all()
然后在 INSTALLED_APPS 中添加:
INSTALLED_APPS = [
...
'django_simpletask5',
]
配置分布式锁(最小可用配置,完整配置项见下方「配置说明」):
DJANGO_SIMPLETASK_LOCK_CONFIG = {
'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
'global_lock_engine_options': {
'host': '127.0.0.1',
'port': 6379,
'db': 0,
},
}
运行迁移:
python manage.py migrate django_simpletask5
配置说明
以下给出面向项目集成的完整 settings.py 配置示例。除 django-simpletask5 相关配置外,其余 Django 默认配置项(DATABASES、MIDDLEWARE、TEMPLATES、ROOT_URLCONF 等)按项目自身需要配置,此处以 ... 省略。配置原则逐项见后文「配置项与配置原则」表。
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'change-me-in-production')
DEBUG = os.environ.get('DJANGO_DEBUG', 'False') == 'True'
ALLOWED_HOSTS = ['*']
# ... 数据库(DATABASES)、中间件、模板、URL 等 Django 默认配置 ...
INSTALLED_APPS = [
# django-simpletask5 的依赖 app(django_safe_fields 等)
# 由 django-app-requires 自动补全,无需手动添加
'django_simpletask5',
# ... 你的业务 app ...
]
# ── 消息队列(MQ)────────────────────────────────────────────
# 生产使用 RabbitMQ 或 Redis;开发调试可用内存队列
DJANGO_SIMPLETASK_BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672//'
DJANGO_SIMPLETASK_BROKER_HEARTBEAT = 60 # 发布连接池的 broker 心跳(秒)
DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES = 100 # AMQP 消息最大重投次数,超过后丢弃
# 若不设 BROKER_URL,则按以下参数组合:
# DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE = 'rabbitmq' # 'memory' | 'rabbitmq' | 'redis'
# DJANGO_SIMPLETASK_RABBITMQ_HOST = '127.0.0.1'
# DJANGO_SIMPLETASK_RABBITMQ_PORT = 5672
# DJANGO_SIMPLETASK_RABBITMQ_USER = 'guest'
# DJANGO_SIMPLETASK_RABBITMQ_PASSWORD = 'guest'
# DJANGO_SIMPLETASK_REDIS_MQ_HOST = '127.0.0.1' # MESSAGE_QUEUE_TYPE='redis' 时
# DJANGO_SIMPLETASK_REDIS_MQ_PORT = 6379
# DJANGO_SIMPLETASK_REDIS_MQ_DB = 0
# DJANGO_SIMPLETASK_REDIS_MQ_PASSWORD = None # Redis MQ 需认证时设置
# ── 分布式锁与 Worker 注册中心(Redis)────────────────────────
# 默认锁引擎 DjangoRedisGlobalLock 复用 CACHES['default'],需为 Redis 缓存
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {'CLIENT_CLASS': 'django_redis.client.DefaultClient'},
},
}
# 如需独立连接参数(锁与 Worker 注册中心共用同一 host/port/db/password):
# DJANGO_SIMPLETASK_LOCK_CONFIG = {
# 'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
# 'global_lock_engine_options': {'host': '127.0.0.1', 'port': 6379, 'db': 0,
# 'password': '<redis 密码,无需认证可省略>'},
# }
# 注:globallock>=0.2.0 已取消按 timeout 自动释放的概念。锁的有效期由
# lockman/lock 作用域自行管理,作用域未结束前锁持续有效(内置看门狗自动续期),
# 锁在 `with` 块退出或进程消亡时自动释放。已移除 DJANGO_SIMPLETASK_LOCK_TIMEOUT
# 与 DJANGO_SIMPLETASK_LOCK_RENEW_INTERVAL,请勿再配置这两项。
# ── Key 前缀与队列路由 ───────────────────────────────────────
DJANGO_SIMPLETASK_KEY_PREFIX = 'myproject' # Redis key 前缀,默认取 ROOT_URLCONF 首段
DJANGO_SIMPLETASK_DEFAULT_QUEUE = 'myproject:django-simpletask5:queue:default'
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'myproject:django-simpletask5:queue:high_priority'
# ── 执行与重试 ───────────────────────────────────────────────
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 执行超时字段(秒,当前不主动中断)
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # Pickup 超时(秒)
# ── 发布重试 ────────────────────────────────────────────────
DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY = {
'max_retries': 2,
'interval_start': 0.2,
'interval_step': 0.3,
'interval_max': 1.0,
}
DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY = 1.0 # 应用层第 1 次重试等待(秒)
DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY = 5.0 # 应用层重试等待上限(秒)
# ── Worker / 日志运维参数 ────────────────────────────────────
DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL = 60 # Worker 心跳/巡检间隔(秒)
DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = 30 # 每个 worker 线程的优雅停机等待(秒)
DJANGO_SIMPLETASK_LOG_INTERVAL = 300 # 存活/注册失败等日志节流间隔(秒)
DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL = 60 # 发布/回收失败日志节流间隔(秒)
# ── 归档与统计 ──────────────────────────────────────────────
# 归档文件通过 TaskExecutionArchive.file 写入 MEDIA_ROOT/django_simpletask5_archives/%Y/%m/
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
DJANGO_SIMPLETASK_ARCHIVE_SALT = 'change-me-archive-salt' # 归档加密盐值
DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS = 7
# ── Cron ────────────────────────────────────────────────────
DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = True # 自动同步代码注册的 Cron
# ── 安全:脚本执行器与字段加密 ───────────────────────────────
DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS = False # 脚本执行器开关(生产保持关闭)
DJANGO_SIMPLETASK_SCRIPT_WHITELIST = [] # 脚本白名单
DJANGO_SIMPLETASK_FIELD_CIPHER_CLASS = None # 自定义加密器类
DJANGO_SIMPLETASK_RESULT_PASSWORD = os.environ.get('DJANGO_SIMPLETASK_RESULT_PASSWORD')
DJANGO_SIMPLETASK_ERROR_MSG_PASSWORD = os.environ.get('DJANGO_SIMPLETASK_ERROR_MSG_PASSWORD')
DJANGO_SIMPLETASK_CONTEXT_PASSWORD = os.environ.get('DJANGO_SIMPLETASK_CONTEXT_PASSWORD')
# ── 可选:Admin Dashboard ────────────────────────────────────
# DJANGO_ADMIN_DASHBOARDS = {
# 'admin:app_list': {'django_simpletask5': 'django_simpletask5.dashboards.TaskDashboard'},
# }
# 必须最后调用:自动补全 django-simpletask5 依赖的 app
from django_app_requires import patch_all as django_app_requires_patch_all
django_app_requires_patch_all()
配置项与配置原则
| 配置项 | 默认值 | 配置原则 |
|---|---|---|
DJANGO_SIMPLETASK_BROKER_URL |
None |
优先级最高,设置后忽略下方 MQ 类型/连接参数。生产建议统一使用完整 URL,便于复用云厂商连接串与凭据管理 |
DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE |
'memory' |
memory 仅用于开发调试;生产必须改为 rabbitmq 或 redis,否则消息不持久化、跨进程 Worker 无法消费 |
DJANGO_SIMPLETASK_RABBITMQ_HOST/PORT/USER/PASSWORD |
127.0.0.1 / 5672 / guest / guest |
类型为 rabbitmq 且未设 BROKER_URL 时生效。生产务必更换默认 guest 凭据 |
DJANGO_SIMPLETASK_REDIS_MQ_HOST/PORT/DB/PASSWORD |
127.0.0.1 / 6379 / 0 / None |
类型为 redis 时生效。建议与锁/缓存使用不同 db,避免 key 冲突与互相阻塞。Redis 需认证时设置 PASSWORD |
DJANGO_SIMPLETASK_BROKER_HEARTBEAT |
60 |
Kombu 发布连接池的 broker heartbeat(秒)。网络中间设备会断开空闲连接时建议保持开启 |
DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES |
100 |
AMQP 消息被 reject/requeue 的最大次数,超过后 ack 丢弃,防止毒消息无限循环;memory/redis 传输无 x-death 时不生效 |
CACHES['default'] |
— | 默认锁引擎 DjangoRedisGlobalLock 依赖它,必须配置为 Redis 缓存 |
DJANGO_SIMPLETASK_LOCK_CONFIG |
None(默认复用缓存锁) |
需要独立连接参数时显式配置(global_lock_engine_options 支持 host/port/db/password)。注意:Worker 注册中心(心跳/状态)也读取该配置的 host/port/db/password,未配置时固定连 127.0.0.1:6379/0。globallock>=0.2.0 起锁有效期由 lockman/lock 作用域管理(内置看门狗自动续期),不再需要配置 LOCK_TIMEOUT / LOCK_RENEW_INTERVAL |
DJANGO_SIMPLETASK_KEY_PREFIX |
自动取 ROOT_URLCONF 首段 |
生产建议显式指定,多项目共用 Redis 时避免 key 冲突 |
DJANGO_SIMPLETASK_DEFAULT_QUEUE |
{prefix}:...:queue:default |
默认队列名。多环境(dev/staging/prod)建议加环境后缀隔离 |
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE |
{prefix}:...:queue:high_priority |
高优先级队列,需单独启动 Worker 消费 |
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES |
3 |
默认最大重试次数。执行器幂等性好的任务可加大 |
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS |
600 |
写入 TaskExecution.timeout_seconds,并用于计算 running 时的 expire_time。当前版本不会因此主动中断 running 任务;严格超时应由执行器自行实现 |
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS |
3600 |
Pickup 超时(等待 Worker 消费的上限),通常大于执行超时,因为排队等待时间不可控 |
DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY |
{'max_retries': 2, 'interval_start': 0.2, 'interval_step': 0.3, 'interval_max': 1.0} |
Kombu Producer 内层重试策略。应用层还会做最多 3 次发布尝试 |
DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY |
1.0 |
应用层发布重试第 1 次等待(秒),之后按指数退避 |
DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY |
5.0 |
应用层单次发布重试等待上限(秒) |
DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL |
60 |
Worker 心跳/巡检间隔,应小于 Worker 注册 TTL(300 秒),否则注册状态被误清理 |
DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT |
30 |
每个 worker 线程的优雅停机等待时长;总等待时间可能为 worker 数乘以该值 |
DJANGO_SIMPLETASK_LOG_INTERVAL |
300 |
Worker 注册失败、Crontab 错误等日志的节流间隔。日志量大时可调大 |
DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL |
60 |
发布、重投、孤儿回收失败日志的节流间隔(秒) |
DJANGO_SIMPLETASK_ARCHIVE_PATH |
django_simpletask5_archives |
当前版本未读取,仅保留兼容。归档文件实际写入 MEDIA_ROOT/django_simpletask5_archives/%Y/%m/ |
DJANGO_SIMPLETASK_ARCHIVE_SALT |
django-simpletask5-archive |
归档加密盐值,生产必须修改(结合 SECRET_KEY 派生 AES 密钥) |
DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS |
7 |
归档保留天数,按合规要求调整 |
DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC |
True |
自动把代码注册的 Cron 同步到数据库;只想手工管理时设为 False |
DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS |
False |
Python/Shell 脚本执行器开关,生产保持 False;开启还需授予对应权限 |
DJANGO_SIMPLETASK_SCRIPT_WHITELIST |
[] |
脚本执行白名单(空列表表示不限制) |
DJANGO_SIMPLETASK_FIELD_CIPHER_CLASS |
None |
自定义字段加密器类(默认 django-safe-fields 内置加密) |
DJANGO_SIMPLETASK_RESULT_PASSWORD |
None |
result 字段加密密码,建议通过环境变量注入,勿硬编码 |
DJANGO_SIMPLETASK_ERROR_MSG_PASSWORD |
None |
error_message 字段加密密码,同上 |
DJANGO_SIMPLETASK_CONTEXT_PASSWORD |
None |
context 字段加密密码,同上 |
注意:
DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS设为True后,还需为用户/组授予django_simpletask5 | Cron job | Can use script executors权限,非超级管理员无法创建 Python/Shell 脚本执行器类型的定时任务。
发布可靠性说明:应用层
_publish_message最多尝试 3 次(首次 + 2 次重试),失败等待按PUBLISH_RETRY_BASE_DELAY指数增长并受PUBLISH_RETRY_MAX_DELAY限制;Kombu Producer 内部还会按PUBLISH_RETRY_POLICY重试。最终仍失败时,safe_publish_message()会把非终态 execution 标记为failed,不会让调用方因 broker 异常崩溃。publish_message()保留异常上抛语义,供需要自行处理错误的调用方使用。
快速开始(从零到一集成)
本框架的集成分三步:定义任务模型 → 编写执行器 → 启动 Worker。完整可运行的参考实现见仓库中的 django_simpletask5_example 应用,包含 OrderTask 模型、创建/更新/删除/退款四个执行器及内置 Cron 任务。
1. 定义任务模型
继承 django_simpletask5.models.Task 即可获得信号驱动的异步能力。模型的保存(新增/更新)与删除会被自动拦截并发布消息,无需手写信号:
from django.db import models
from django_simpletask5.models import Task
class OrderTask(Task):
order_id = models.CharField(max_length=64, unique=True)
customer_name = models.CharField(max_length=128)
amount = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=32, default='pending')
# 事件 → 执行器映射(详见「事件处理机制」)
executor_class = {
'create': 'myapp.executors.OrderCreateExecutor',
'update': 'myapp.executors.OrderUpdateExecutor',
'delete': 'myapp.executors.OrderDeleteExecutor',
'refund': 'myapp.executors.OrderRefundExecutor', # 自定义事件,由 request_refund 通过 self.trigger('refund') 手动触发
}
# 事件 → 队列路由(未映射的事件自动落到 DJANGO_SIMPLETASK_DEFAULT_QUEUE)
simpletask_queue = {
'create': 'myproject:django-simpletask5:queue:high_priority',
'update': 'myproject:django-simpletask5:queue:default',
}
# 仅当这些字段发生变化时才触发 update 事件(详见「事件处理机制」)
trigger_update_fields = ['customer_name', 'amount', 'status']
def request_refund(self, amount):
return self.trigger('refund', extra_context={'refund_amount': str(amount)})
要点:
- 主键为 Django 默认自增
id。Task不再内置task_id字段,业务标识由业务模型自行定义(自定义主键或自然键均可) - 保存 / 删除 / 批量创建自动触发事件,详见「事件处理机制」
- 事件上下文、update 字段判定、delete 拦截等行为的完整说明见对应章节
2. 编写执行器
执行器继承 BaseExecutor,实现 execute() 方法即可:
# myapp/executors.py
from django_simpletask5.executors.base import BaseExecutor
from django_simpletask5.models import TaskExecution
class OrderCreateExecutor(BaseExecutor):
timeout_seconds = 300 # 记录预期执行上限(当前不主动中断,可选)
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟(可选)
def execute(self, execution: TaskExecution, task=None) -> str | None:
context = execution.get_context_dict() # 事件上下文
order = context['data'] # create 事件的模型字段快照
# ... 你的业务逻辑(调用外部系统、发消息等)...
return 'ok' # 返回值存入 execution.result
执行器接口、批量任务、异常重试、拒绝回退等完整说明见「应用开发:执行器」。
3. 启动 Worker
python manage.py django_simpletask_executor --workers 4
- 默认监听
default队列;high_priority队列需单独启动 Worker 处理 - 可用
--service myapp.executors.OrderCreateExecutor只处理指定执行器 - 可用
--debug输出完整异常堆栈与 DEBUG 日志 - Worker 线程由
WorkerSupervisor守护:异常退出自动重启、保持目标 Worker 数量;Redis/MQ 断连自动重连
4. 启动 Cron 调度(可选)
python manage.py django_simpletask_crontab
5. 触发自定义事件
在模型方法或业务代码中调用 trigger() 即可发布任意事件:
order = OrderTask.objects.get(order_id='ORD-001')
order.trigger('refund', extra_context={'refund_amount': '50.00'})
6. 验证
启动 Worker 后新增一条 OrderTask,观察数据流转:
OrderTask.objects.create(order_id='ORD-001', customer_name='Test', amount=Decimal('100.00'))
- 数据库
TaskExecution表新增一条create事件、pending状态的记录 - Worker 消费后状态变为
running,执行完成变为success,result保存执行器返回值 - Django Admin 的 Dashboard 可查看执行统计与 Worker 状态
事件处理机制
内置事件:create / update / delete
框架通过 Django 信号自动监听所有 Task 子类的保存与删除,无需手动发布:
| 事件 | 触发时机 | 执行器可见的上下文 |
|---|---|---|
create |
新增实例(save() / bulk_create()) |
task_id、trigger_event、data(全字段快照) |
update |
字段值发生变化后保存 | task_id、trigger_event、data、old_data、new_data、changed_fields |
delete |
调用 instance.delete() |
task_id、trigger_event、data |
执行器通过 execution.get_context_dict() 获取以上上下文。其中 task_id 是业务对象主键的字符串表示(如 "42"、UUID 字符串或自定义键);业务对象反查见「模型与生命周期」。
update 事件的字段变更判定
update 事件与保存动作的关系:框架每次保存已存在的实例时,通过 pre_save/post_save 信号对比旧值快照与新值,计算发生变化的字段列表(changed_fields)。默认只要任一字段变化就发布 update 事件——这往往会产生大量非预期消息。例如某次保存只改了一个内部计数或审计字段,下游系统却会收到一次同步通知。
为此可用类属性控制「哪些字段变化才值得触发 update 事件」:
class OrderTask(Task):
# 白名单:仅当这些字段之一发生变化时才发布 update 事件
trigger_update_fields = ['customer_name', 'amount', 'status']
# 或黑名单:这些字段变化不发布 update 事件,其余字段变化照常发布
# trigger_update_blacklist = ['sync_count', 'updated_by']
| 配置方式 | 行为 | 适用场景 |
|---|---|---|
| 都不配置(默认) | 任何字段变化都触发 update 事件 | 简单模型 |
trigger_update_fields |
仅当白名单内的字段变化时触发 | 只在关键业务字段变更时同步下游(推荐) |
trigger_update_blacklist |
白名单外的字段变化触发;黑名单内字段变化不触发 | 忽略噪声字段(内部计数、审计字段等) |
白名单与黑名单二选一,同时配置时以白名单为准。无论何种方式,值未实际变化(如调用 save() 但所有字段值相同)的保存都不会触发 update 事件。changed_fields 中即包含本次实际发生变化的字段名列表,执行器可据此做增量处理。
delete 事件的拦截与确认删除
Task.delete() 被框架拦截,不会立即删除数据库记录,而是发布 delete 事件消息。执行器处理完外部系统清理后,调用 execution.confirm_delete() 完成真实删除:
class OrderDeleteExecutor(BaseExecutor):
def execute(self, execution: TaskExecution, task=None) -> str | None:
# ... 先清理外部系统关联数据 ...
execution.confirm_delete() # 真正删除对应 Task 记录
return 'deleted'
这样实现删除操作与外部系统清理的解耦,且支持失败重试。
confirm_delete() 与 execution.get_task() 都会通过 TaskExecution.task_id(业务主键字符串)反查业务对象,反查逻辑由业务模型上的 get_task_instance() 提供(见「模型与生命周期」)。
事件 → 执行器映射
每个事件消息最终要由一个执行器处理。executor_class 声明「事件名 → 执行器类路径」的分派表,可以是字符串(所有事件共用同一个执行器)或字典(按事件分派):
executor_class = {
'create': 'myapp.executors.OrderCreateExecutor',
'update': 'myapp.executors.OrderUpdateExecutor',
'delete': 'myapp.executors.OrderDeleteExecutor',
'refund': 'myapp.executors.OrderRefundExecutor', # 自定义事件
'default': 'myapp.executors.OrderDefaultExecutor', # 兜底执行器(可选)
}
执行器解析顺序:命中事件名(如 refund 映射到 OrderRefundExecutor)→ default → create → 自动派生 app_label.tasks.{ClassName}{Event.capitalize()}Executor(以上都未命中时的兜底路径)。
注意:
executor_class只负责「事件 → 由谁执行」的映射,与事件由谁触发无关。内置事件(create/update/delete)由信号自动触发;自定义事件(如refund)必须由业务代码调用trigger('refund')手动触发,详见下方「自定义事件」。
事件 → 队列路由
每个事件消息发布到哪个队列,由 simpletask_queue 决定。可以是字符串(所有事件同一队列)或字典(按事件路由):
simpletask_queue = {
'create': 'myproject:django-simpletask5:queue:high_priority',
'update': 'myproject:django-simpletask5:queue:default',
}
队列解析顺序:命中事件名 → default → 全局默认队列 DJANGO_SIMPLETASK_DEFAULT_QUEUE。
以上面只配置 create/update 两个键为例,各事件的落队结果:
| 事件 | 解析结果 |
|---|---|
create |
命中键 → 高优先级队列 |
update |
命中键 → 默认队列 |
delete(内置) |
未命中、无 default 键 → 全局默认队列 DJANGO_SIMPLETASK_DEFAULT_QUEUE |
refund(自定义) |
同上 → 全局默认队列 |
即:只映射了部分事件时,其余未映射的事件自动落到 DJANGO_SIMPLETASK_DEFAULT_QUEUE。如需统一兜底到某个队列,可显式添加 'default' 键。
自定义事件
与信号自动触发的内置事件不同,自定义事件(如 refund)不会自动触发,需要业务代码调用 Task.trigger(event, extra_context=None) 手动发布:
order = OrderTask.objects.get(order_id='ORD-001')
order.trigger('refund', extra_context={'refund_amount': '50.00'})
# 触发后流程:
# trigger('refund') → 创建 trigger_event='refund' 的 TaskExecution
# → 按 simpletask_queue 路由发布消息 → Worker 按 executor_class['refund']
# 加载 OrderRefundExecutor 执行
- 事件名任意:
trigger()的第一个参数即事件名,需与executor_class/simpletask_queue字典中的键保持一致,否则走default或兜底路径 - 执行器:按「事件 → 执行器映射」解析
- 队列:按「事件 → 队列路由」解析
- 上下文:
extra_context会合并进执行器可见的事件上下文,执行器侧读取context['refund_amount']:
class OrderRefundExecutor(BaseExecutor):
def execute(self, execution, task=None):
context = execution.get_context_dict()
refund_amount = context['refund_amount'] # == '50.00'
...
抑制异步消息
临时禁止某个实例发布消息(_skip_publish 为临时实例属性,不会持久化到数据库),四种使用方式:
order._skip_publish = True; order.save() # 方式一:实例属性
order.skip_publish().save() # 方式二:链式方法
order.save(skip_publish=True) # 方式三:save 参数
OrderTask.objects.create(..., skip_publish=True) # 方式四:构造参数(推荐)
不会创建 TaskExecution,也不会发送异步消息。
应用开发:执行器
BaseExecutor 接口
| 成员 | 说明 |
|---|---|
execute(execution, task=None) |
必须实现。执行任务,返回值存入 execution.result |
get_tasks(execution) |
返回可迭代对象,默认 yield None(单任务) |
get_tasks_count(execution) |
任务总数(用于进度展示),默认 1 |
reject(execution, reason='') |
拒绝任务:状态复位 pending、清空 started_at、刷新 expire_time |
timeout_seconds / pickup_timeout_seconds |
类属性。pickup_timeout_seconds 用于自动超时判定;timeout_seconds 当前仅记录预期执行上限,不主动中断任务 |
get_parameter_schema() |
声明执行器参数,用于 Admin 参数配置界面(默认空) |
execute 签名与返回值
def execute(self, execution: TaskExecution, task=None) -> str | None:
context = execution.get_context_dict() # 事件上下文(见「事件处理机制」)
# ... 业务逻辑 ...
return 'result'
- 事件上下文:
execution.get_context_dict()返回反序列化后的 JSON 上下文 - 返回值:单个任务时直接返回(字符串或 JSON 字符串);返回
None时result保持为空
批量任务
重写 get_tasks() 与 get_tasks_count(),一个执行器即可处理多条子任务:
class BatchSyncExecutor(BaseExecutor):
def get_tasks_count(self, execution):
return len(self._ids(execution))
def get_tasks(self, execution):
for _id in self._ids(execution):
yield _id
def _ids(self, execution):
return execution.get_context_dict().get('ids', [])
def execute(self, execution, task=None):
self._sync(task) # task 为 get_tasks() 产出的单个子任务
return 'done'
批量执行时 execution.result 聚合为 {"results": [...], "total": N},每完成一个子任务更新 done_tasks_count 便于进度展示。
异常与状态流转
执行器抛出的异常由框架统一处理:
| 情况 | 结果 |
|---|---|
| 无异常 | 状态 success,result 保存返回值 |
RejectException |
拒绝回退:状态复位 pending,消息重新入队,不消耗重试次数 |
其他异常且 retry_count < max_retries |
状态 retry,retry_count + 1,消息重新入队 |
| 其他异常且重试次数已用完 | 状态 failed,error_message 记录异常信息 |
拒绝回退
执行器遇到临时条件不满足(如资源上限),抛出 RejectException 拒绝本次执行:
from django_simpletask5.executors.exceptions import RejectException
class MyExecutor(BaseExecutor):
def execute(self, execution: TaskExecution, task=None) -> str | None:
if not self._can_proceed():
raise RejectException('Resource limit reached, try again later')
框架捕获后:
- 调用
message.reject(requeue=True)将消息放回队列尾部,下次重新调度 TaskExecution状态恢复为pending,started_at清空,等待下次 Worker 消费- 不消耗重试次数
- 刷新
expire_time(使用 Pickup 超时),防止被StatusCheckExecutor误回收
超时配置
class OrderCreateExecutor(BaseExecutor):
timeout_seconds = 300 # 记录预期执行上限(当前不主动中断)
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
未配置时使用全局默认值 DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS(600 秒)与 DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS(3600 秒)。
注意:当前版本只有
pickup_timeout_seconds会被框架自动判定并触发timeout;timeout_seconds不会中止正在运行的执行器。需要严格执行时限时,请在execute()内部自行实现超时或使用可取消的子进程。
模型与生命周期
Task 抽象模型
所有业务任务模型继承 Task:
- 主键为 Django 默认自增
id,无需自定义;不内置task_id字段,业务标识由业务模型按需定义 - 提供
trigger(event, extra_context)、skip_publish()、get_executor_class_path(event)、get_queue_name(event)等方法 - 保存、删除、批量创建自动触发事件(见「事件处理机制」)
业务对象反查:get_task_instance()
TaskExecution.task_id 存的是业务对象主键的字符串表示(CharField)。需要反查业务对象时(execution.get_task() / execution.confirm_delete()),框架先通过 task_model 解析出模型类,再调用该类的 get_task_instance(task_id) 完成查询。
默认实现在 Task 基类中按主键 pk 查询,并通过内置的 _coerce_task_pk() 兼容常见主键类型:
AutoField/BigAutoField/SmallAutoField— 字符串转为intUUIDField— 字符串转为uuid.UUIDCharField/TextField等字符串主键 — 原样使用
task = execution.get_task() # 返回业务对象(反查不到返回 None)
execution.confirm_delete() # 反查并真实删除业务对象
业务模型使用自定义主键或非主键自然键时,重载 get_task_instance() 即可,框架会自动改走你的实现:
class OrderTask(Task):
code = models.CharField(max_length=64, unique=True) # 非主键自然键
@classmethod
def get_task_instance(cls, task_id):
if task_id is None:
return None
return cls.objects.filter(code=task_id).first()
TaskExecution 状态机
每条事件消息对应一条 TaskExecution 记录,状态流转如下:
pending ──(Worker 消费)──▶ running ──(执行成功)──▶ success
│ │
│ ├─(异常且可重试)────────▶ retry ──(重新入队并被消费)──▶ running
│ ├─(异常且重试耗尽)──────▶ failed
│ └─(执行器异常退出/锁丢失)─▶ retry 或 failed(由 reclaim 回收)
├─(pickup 超时,从未被领取)──▶ timeout ──(RetryTimeout)──▶ retry
├─(用户取消)────────────────▶ canceled(终态)
└─(发布最终失败)────────────▶ failed
| 状态 | 含义 |
|---|---|
pending |
已发布,等待 Worker 消费;拒绝回退也会回到该状态 |
running |
已获取执行锁,正在执行 |
success |
执行成功 |
failed |
重试次数耗尽、发布最终失败,或孤儿回收时已无重试次数 |
retry |
失败/超时后等待重新投递,消息已重新入队 |
timeout |
pending 超过 pickup 期限仍未被领取;running 不会因 expire_time 自动变成 timeout |
canceled |
被用户主动取消(终态,Worker 不会执行) |
取消任务
已发布但未执行(pending)的任务可通过 Admin 列表页勾选后执行 「Cancel selected pending tasks」 操作批量取消,状态置为 canceled。代码方式:
from django_simpletask5.models import TaskExecution
TaskExecution.objects.filter(
execution_id='<execution_id>', status='pending',
).update(status='canceled')
canceled 为终态:即使消息仍留在 MQ 队列中,Worker 消费时也会直接跳过(不会执行),且不会被归档统计混入 failed。已进入 running 的任务无法取消。
重试与超时
- 重试:
max_retries(默认 3)控制最大重试次数,retry_count累计已重试次数;重试消息重新发布到原队列 - Pickup 超时(
pickup_timeout_seconds):唯一由框架自动判定的超时。pending超过expire_time后由StatusCheckExecutor标记为timeout,也用于拒绝回退后的重新调度保护;Pickup 超时不消耗重试次数 - 执行超时字段(
timeout_seconds):写入TaskExecution.timeout_seconds,并在任务进入running时用于计算expire_time。当前版本不会因该时间到达而主动中断 running 任务;如果执行器 hang 住,需在执行器内部实现超时/取消机制 - 超时检测与恢复:
StatusCheckExecutor每分钟把超过 pickup 期限的pending记录标记为timeout;RetryTimeoutExecutor每分钟把timeout记录重新入队,实际运行过的记录消耗一次重试次数。running 孤儿任务由 crontab 基于执行锁过期回收,而不是按expire_time判超时
其他模型
CronJob— 定时任务注册表,支持代码注册与数据库覆盖(见「Cron 任务」)TaskExecutionArchive— 归档记录,终态执行记录归档为加密 JSONL(见配置「归档」)TaskExecutionStat— 按日期、executor_class、trigger_event聚合的日统计
Cron 任务
在代码中定义 Cron 任务
在任意 app 的 cronjobs.py 文件中使用 register_cronjob 注册,并在该 app 的 AppConfig.ready() 中导入这个模块;框架不会自动发现各 app 下的 cronjobs.py:
# myapp/cronjobs.py
from django_simpletask5.core.cronjob_registry import register_cronjob
register_cronjob(
uid='health_check',
display_name='Health Check',
cron_expression='*/5 * * * *',
executor_class='django_simpletask5.executors.simple_request.SimpleRequestExecutor',
context={
'url': 'https://example.com/health',
'method': 'GET',
'timeout': 10,
},
description='定期健康检查',
)
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
import myapp.cronjobs # noqa: F401
从代码同步到数据库
注册的 Cron 任务需要同步到数据库才会生效。django_simpletask_crontab 运行期间每个 tick 都会自动同步(默认开启,可通过 DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = False 关闭自动同步),也可以手动执行:
python manage.py django_simpletask_sync_cronjobs
手动同步命令使用 sync_cronjobs(force=True),即使关闭了自动同步也会执行。同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(is_modified_by_user 标记保护)。单个 CronJob 创建失败只记录日志,不会中断其它任务同步。
启用/停用(is_active)的所有权独立跟踪,规则与 is_modified_by_user 对称——手动改过就一直归你,直到显式重置:
- 用户未手动启停过的任务,
is_active会按代码注册值同步(例如simpletask_ping_pong注册为is_active=False,默认保持停用)。 - 用户手动暂停/启用后,会置
is_active_modified_by_user标记,自动同步不再覆盖该状态,但不影响其它定义字段(cron_expression等仍照常同步)。 - 该标记不会自动清除;需要让它重新跟随代码时,使用管理后台的“从代码定义重置”(
reset_from_code),或在编辑页取消勾选“保留手动启用状态”。 - 这样手动启停的状态可稳定保持,不会因代码默认值变化而悄悄被改写。
注意:cron 表达式按 Django 的
TIME_ZONE设置对应的本地时间解释。USE_TZ=True时仍以本地时间计算触发时刻(存储为 UTC),例如TIME_ZONE = 'Asia/Shanghai'下30 14 * * *表示每天 14:30 本地时间触发。
内置执行器
| 执行器 | 说明 |
|---|---|
PingPongExecutor |
健康检查,返回 'pong';内置 Cron 默认停用 |
BashScriptExecutor |
执行 Shell 脚本 |
PythonScriptExecutor |
执行 Python 代码 |
SimpleRequestExecutor |
发起 HTTP 请求 |
StatusCheckExecutor |
检测超过 pickup 期限、从未被领取的 pending 记录并标记 timeout(内置 Cron 每分钟) |
RetryTimeoutExecutor |
将 timeout 记录重新入队(内置 Cron 每分钟;运行过的记录消耗一次重试) |
ArchiveExecutor |
归档前一天的终态执行并生成统计(内置 Cron 每天 2:00) |
PurgeExpiredExecutor |
删除超过 ARCHIVE_RETENTION_DAYS 的终态执行记录(内置 Cron 每天 3:00;不删除归档文件) |
开发与测试
项目提供 start-test-services.sh 一键启动测试所需的 Redis 与 RabbitMQ 服务。脚本优先检测本地已拉取的镜像(避免依赖 docker.io 上游镜像),也可通过 DJANGO_SIMPLETASK_REDIS_IMAGE / DJANGO_SIMPLETASK_RABBITMQ_IMAGE 环境变量指定,默认使用 6379/5672 端口(被占用时自动选取空闲端口):
./start-test-services.sh start # 启动服务并等待就绪
./start-test-services.sh status # 查看容器与端口状态
./start-test-services.sh stop # 停止并清理容器
运行测试:
python3 -m pytest django_simpletask5_example/tests
测试套件覆盖 Redis、DB、MQ 连接中断后的自动恢复场景,详见 django_simpletask5_example/tests/test_resilience_recovery.py。
测试输出约定:单元测试中对预期会触发的错误(如无效 UUID 校验、毒消息丢弃、消费者断连、执行器加载失败等)所输出的错误日志,必须在测试内屏蔽,避免污染测试输出。统一使用 django_simpletask5_example.tests.suppress_expected_errors 上下文管理器(整体禁用日志)或 patch.object(..., 'logger')(断言同时校验日志)进行屏蔽。
架构
Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息队列
↓
Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
↓
失败时自动重试/拒绝时重新入队,完成后归档
Releases
0.2.9
- 启用状态所有权独立跟踪 — 新增
CronJob.is_active_modified_by_user字段与迁移0014,将“启用/暂停状态”与“定义字段”的所有权彻底分离。用户手动启停(快捷按钮 / 批量操作 / 编辑表单)后置该标记,自动同步(sync_cronjobs)不再覆盖is_active;未手动改过的任务仍按代码注册值同步。暂停只锁is_active,cron_expression等定义字段照常同步 - 显式释放所有权 — 该标记不会自动清除;需要让任务重新跟随代码时,使用管理后台“从代码定义重置”(
reset_from_code),或在编辑页取消勾选“保留手动启用状态” - 保留列表分页/过滤条件 — 修复暂停/运行按钮提交后重定向丢失查询串的问题:按钮表单
action及视图重定向均携带当前 changelist 的GET参数,操作后刷新仍停留在原分页、过滤器与搜索条件 - 消息发布可靠性增强 — Kombu 发布复用连接池并支持 broker heartbeat;
publish()返回queue/message_count/consumer_count队列统计;新增可配置发布重试策略和递增重试延迟;发布异常时自动丢弃损坏连接;新增公开的publish_message()与兜底的safe_publish_message(),最终发布失败会将 execution 标记为failed - 发布日志节流 — 发布、重试投递、孤儿任务回收和 CronJob 触发失败统一接入日志节流,避免 broker/Redis 长时间不可用时刷屏;重投日志同时记录队列积压与消费者数量
- Worker 与回收健壮性 — Worker 注册表 Redis 客户端改为公开
get_redis_client();修正 Worker 清理 key 的 glob 为worker:{pid}:*;reclaim._lock_config()与get_lock_manager()对齐,只有None才回退默认锁配置;回收扫描前后关闭旧数据库连接,减少长时间运行后的连接失效 - 日志初始化修复 —
setup_console_logging()不再把FileHandler误判为控制台 handler,避免已有文件日志时跳过控制台输出 - CronJob 同步健壮性 —
sync_cronjobs(force=True)支持手动同步命令绕过DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC;单条 CronJob 创建失败不再中断其它任务同步;django_simpletask_sync_cronjobs命令改为强制同步 - API 命名整理 —
_CRONJOB_REGISTRY→CRONJOB_REGISTRY、_compute_next_run_time()→compute_next_run_time()、_get_broker_url()→get_broker_url()、_model_to_dict()→model_to_dict()、_DeleteIntercepted→DeleteIntercepted、_get_redis_client()→get_redis_client();依赖旧私有名称的扩展或测试需同步调整 - 可观测性入口公开 — Dashboard 使用的 Worker 统计入口由
_get_worker_stats改为公开的get_worker_stats();消费者线程心跳改为读取DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL,Worker 注册、Redis 降级和日志节流逻辑补充了完整说明 - 注释与文档校准 — 为整个
django_simpletask5生产包补充中文模块/类/函数/分支注释,并修正归档路径、归档清理、running/timeout状态流转、Admin 只读性、脚本白名单、连接池 heartbeat、优雅退出等待时间等与实际行为不一致的说明 - 迁移与翻译 — 新增迁移
django_simpletask5.0014_cronjob_is_active_modified_by_user,同步更新简体中文翻译与迁移中的字段help_text - 测试 — 新增
test_logging_utils.py;扩充消息发布、消息队列、CronJob 同步、Admin 启停、Reclaim、Worker 恢复、Redis 锁与集成测试,覆盖最终发布失败、队列积压统计、force同步、启停所有权、查询串保留、空锁配置和 Worker 清理 glob 等场景 - 环境要求 — 代码使用
X | None联合类型,最低 Python 版本明确为 3.10;同时将 Django 最低版本明确为 4.2,并补充Django>=4.2依赖声明 - 版本 —
pyproject.toml版本更新为0.2.9
0.2.8
- 移除
Task冗余的task_id字段 —TaskExecution.task_id由 UUID 改为CharField(max_length=255),直接用于存放业务对象主键字符串(str(instance.pk)),不再维护一份与业务主键重复的 UUID。消息上下文中的task_id字段名不变,值语义从 UUID 变为业务主键字符串(如"42") - 业务对象反查下沉到模型层 — 新增
Task.get_task_instance(task_id)类方法作为默认反查实现(按pk查询,内置_coerce_task_pk()兼容int/UUID/ 字符串主键);TaskExecution.get_task()/confirm_delete()改为解析task_model后委托给该方法。业务模型使用自定义主键或自然键时重载get_task_instance()即可 - 新增迁移 —
django_simpletask5.0013_alter_taskexecution_task_id(字段改 CharField)、django_simpletask5_example.0003_remove_ordertask_task_id - 破坏性变更: 依赖
TaskExecution.task_id为 UUID 类型的外部消费方需适配为业务主键字符串;业务Task子类若曾使用内置task_id字段需移除
0.2.7
- 运行中任务回收机制重构 — 取消原先按
expire_time将 running 直接判为超时的语义;StatusCheckExecutor仅兜底回收长期未被 Worker 获取的 pending。新增core/reclaim,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间 - 重试不再搁浅任务 — 失败重试先落库
retry再发布重试消息,发布失败则 requeue 当前消息,杜绝任务永久停在无人处理的retry状态;KombuQueue 依据 AMQPx-death上限丢弃毒消息,避免无限 requeue 造成的队头阻塞与空转 - 消费者重连提示 — Worker 断连重连成功后输出
Consumer connection restored恢复日志,便于运维确认消费通道已恢复 - 安全与健壮性加固 — broker 账号/密码按百分号编码、脚本白名单按路径段边界匹配、signals 快照按实例隔离,覆盖相关回归测试
- 新增测试约定 — 单元测试中对预期触发(如无效 UUID 校验、毒消息丢弃、消费者断连、执行器加载失败等)产生的错误日志必须屏蔽输出,统一使用
django_simpletask5_example.tests.suppress_expected_errors或替换 logger,保证测试输出干净可读;本次同步清理全部此类预期错误日志
0.2.6
- 新增
canceled状态 — 取消已发布但未执行的任务改为独立的终态canceled(原复用failed),Worker 消费到已取消消息时直接跳过;Admin 取消操作、归档/清理/统计(新增canceled_count)、Dashboard 状态图均纳入该状态,取消与执行失败可清晰区分 - WorkerSupervisor 守护线程 — 统一的 Worker 线程保活机制,周期性心跳并自动重启异常退出的 Worker 线程,保持目标 Worker 数量,提升长时间运行稳定性
- 韧性/恢复测试套件 — 新增
test_resilience_recovery.py,模拟 Redis、DB、MQ 连接中断后的自动恢复:Worker 启动时 Redis 注册失败重试、消费者断连自动重连、锁引擎中断后消息重入队并在 Redis 恢复后继续处理、DB 瞬断重入队、执行器异常标记 failed/retry、crontab 调度器 Redis 恢复检测等 - 测试套件稳定性修复 —
test_worker_registry隔离全局_active_workers消除跨用例污染;test_integration动态探测本地可用的 Redis/RabbitMQ 镜像(支持DJANGO_SIMPLETASK_REDIS_IMAGE/DJANGO_SIMPLETASK_RABBITMQ_IMAGE环境变量覆盖),并改为真实 AMQP 握手探测 RabbitMQ 就绪,避免新建容器连接被重置的偶发失败 - 测试服务脚本 —
start-test-services.sh默认使用 6379/5672 端口并动态检测本地镜像,提供start/stop/status子命令,便于一键启动测试依赖
0.2.5
- 修复 0010 迁移双主键冲突 — 修正迁移
0010_taskexecution_id_alter_taskexecution_execution_id的操作顺序:先移除execution_id的主键约束(改为unique=True),再添加自增id主键,避免在已有数据库上升级时因同时存在两个主键而失败。已有 0.2.4 数据可正常升级,id自动回填,execution_id保持唯一
0.2.4
- 破坏性变更:
TaskExecution主键由execution_id(UUID)改为默认自增id,execution_id变为unique=True的唯一标识字段。已有数据库需要执行迁移0010_taskexecution_id_alter_taskexecution_execution_id - 定期存活日志 — Worker 与 Crontab 主循环在长时间无消息/无任务时按
DJANGO_SIMPLETASK_LOG_INTERVAL(默认 300 秒)定期输出存活日志,并统一带时间戳格式,便于运维确认进程未死循环 - MQ 日志降噪 — MQ 消费/发布异常及 Redis 连接失败改为
error/warning级别且无堆栈(默认按间隔节流),避免刷屏;提供--debug时输出完整堆栈。注:此处所指的"锁续期失败"是 0.2.4 版本引入的自定义锁续期机制,自 globallock>=0.2.0 起该机制已移除,改由 globallock 内置看门狗在 lockman/lock 作用域内自动续期,无需(也无从)单独记录续期失败日志 - 新增配置 —
DJANGO_SIMPLETASK_LOG_INTERVAL控制死循环存活日志与异常节流日志的输出间隔(秒)
0.2.3
- Cron 时区修复 — cron 表达式改为按 Django
TIME_ZONE配置的本地时间解释,不再固定按 UTC 计算。USE_TZ=True时使用timezone.localtime(now())作为 croniter 基准时间,非 UTC 时区下定时任务不再偏移;USE_TZ=False保持原有本地时间行为不变
0.2.2
- 抑制异步消息 — 新增
_skip_publish机制,创建/更新/删除 Task 时可通过skip_publish()方法、save(skip_publish=True)、构造时传入skip_publish=True等方式禁止触发 RabbitMQ 消息通知 - 批量创建触发异步消息 —
TaskQuerySet+TaskManager接管bulk_create,批量创建 Task 后自动生成 TaskExecution 并推送 MQ;子类覆盖objects时发出警告提示 - 分布式锁默认引擎 — 默认使用
DjangoRedisGlobalLock,复用 DjangoCACHES['default']配置(需为 Redis 缓存),无需手动配置DJANGO_SIMPLETASK_LOCK_CONFIG - Redis key 命名统一 — 所有 Redis key 格式规范为
{项目前缀}:django-simpletask5:{用途}:{id},项目前缀从ROOT_URLCONF自动提取,也可通过DJANGO_SIMPLETASK_KEY_PREFIX显式覆盖 - CronJob 管理增强 — 新增
app_label字段(自动检测),列表页展示并支持过滤 - 内置 CronJob 重命名 — Status Check → Task Timeout Detection,Retry Timeout → Retry Failed Tasks,Ping/Pong → Health Check 等
- Dashboard 重做 — 新增今日失败卡片,成功率按 90%/80% 分绿色/橙色/红色三级,卡片颜色统一使用 info 蓝色
- Pending/Running 显示修复 — 改为展示
pending / running双值而非仅 pending - 成功率显示优化 —
(22/25)与88%同行显示,字号缩小颜色淡化,高度对齐 - Redis 不可达降级 — Worker 数量显示
-而非0,区分无数据与异常 - 按钮优化 — 改用 Font Awesome 图标,统一
min-width对齐,title 改为中文描述 - 翻译补充 — 补充 Run/Activate/Deactivate 及所有内置 CronJob 名称的中文翻译
- 新增依赖 —
django-static-fontawesome用于按钮图标 - 示例应用 — 示例 cronjobs 模块在 AppConfig.ready() 中导入确保注册生效
0.2.1
- 拒绝回退机制 — 新增
RejectException,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数 - 区分两种超时 — 引入
pickup_timeout_seconds(等待 Worker 消费超时)与timeout_seconds(执行超时)两种超时配置,expire_time改用 Pickup 超时计算,超时检测更精准 - 文档更新 — README 补充拒绝回退机制的使用说明和两种超时的详细解释
0.2.0
- 破坏性变更: 移除
Task模型上的自定义主键,task_id不再是主键字段,改为unique=True的唯一标识字段。所有Task子模型将自动获得 Django 默认的自增id主键。已有数据库需要迁移处理。 - 统计维度调整:
TaskExecutionStat的统计维度由task_model改为executor_class,统一覆盖有模型任务和 Cron 定时任务两种场景。对应 API_update_stats()按executor_class分组聚合。 - Admin 优化: CronJob 列表页移除
executor_class列避免表格撑开;TaskExecutionStat 列表页新增executor_class列、created_at设为只读修复详情页错误。 - 测试修复: 修复
TransactionTestCase+on_commit在 SQLite 共享内存连接下因TestCase残留原子块导致回调不执行的兼容性问题;Redis MQ / RabbitMQ E2E 集成测试全部通过。
0.1.5
- 安全加固 — 新增
DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS配置项(默认关闭),启用后才可执行 Python/Shell 脚本;新增can_use_script_executors权限,仅有此权限或超级管理员的用户才能在 Admin 中创建脚本执行器类型的 CronJob - Bug 修复 — 修复
RetryTimeoutExecutor中 lambda 晚绑定导致所有回调引用最后一个记录的问题 - Admin 安全 — 操作按钮(立即执行、启用/禁用)从 GET 参数改为 POST 请求,附带 CSRF 保护
- 性能优化 —
RetryTimeoutExecutor改为批量 update + 按 ID 分发消息推送 - Admin 增强 —
TaskExecutionAdmin新增executor_class过滤、error_message搜索、批量重试失败任务、批量取消待处理任务 - 数据索引 — 添加
(status, expire_time)和(is_active, next_run_time)复合索引;trigger_event、executor_class、task_id、created_at添加单字段索引 - 代码规范 —
bash_script.py、python_script.py、simple_request.py补充缺失的 logger;移除废弃的allow_tags属性
0.1.4
- 修复打包 —
pyproject.toml添加package-data配置,打包时包含 locale po/mo 文件,修复 i18n 翻译不生效的问题 - 补充依赖 —
requirements.txt和pyproject.toml添加缺失的django-checkbox-normalize、django-tabbed-changeform-admin依赖 - 完善文档 — README 新增完整配置说明章节,涵盖消息队列(MQ)、分布式锁、队列路由、归档、Cron、安全等全部配置项
0.1.3
- Admin 界面大升级 — 集成 Tabbed ChangeForm 分页签展示字段,CronJob 后台支持直接执行/启用/禁用操作;TaskExecution 列表优化,execution_id 显示前8字符并支持点击复制,重试次数合并显示,全局字段中文翻译
- 归档管理增强 — 新增
django_simpletask_archive管理命令,支持手动触发归档与过期清理;PurgeExpiredExecutor 定期自动清理过期记录;保留天数可配置;采用流式 AES-GCM 加密,大文件归档更高效 - Dashboard 修复 — 卡片链接使用
reverse正确跳转,无执行数据时成功率显示- - 超时检测优化 — 模型新增
expire_time字段,超时判定更准确高效
0.1.2
- 修复: 修复 Transaction-Message 排序竞态条件,使用
transaction.on_commit确保消息在事务提交后才发送到队列
0.1.1
- 修复: 修复
DjangoSimpletask5Config缺少ready()方法导致信号监听未注册的问题
0.1.0
这是 django-simpletask5 的首个正式版本。核心功能包括:
- 声明式任务模型 — 继承
Task模型即可定义任务,自动处理创建/更新/删除事件 - 信号驱动自动发布 — Django 信号自动拦截模型变更,发布
TaskExecution到消息队列 - Worker 异步执行 — 多 Worker 进程消费消息队列,支持队列路由和优先级
- Cron 定时调度 — 内置 crontab 守护进程,支持代码注册与数据库覆盖
- 重试与超时 — 失败自动重试(指数退避),支持超时检测
- 加密字段 — 敏感数据自动加密存储
- 归档统计 — 已完成执行记录自动归档为加密 JSONL,并生成日统计
- Worker 注册中心 — 基于 Redis 的 Worker 心跳与状态追踪
- Django Admin 集成 — 完整的后台管理界面与仪表盘
许可证
MIT
Metadata
Release files for django-simpletask5 0.2.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| django_simpletask5-0.2.9.tar.gz | 140.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_simpletask5-0.2.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 268.4 kB
Release files / django_simpletask5-0.2.9.tar.gz
| Download URL | django_simpletask5-0.2.9.tar.gz |
|---|---|
| Size | 140.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
14cb329dd1e221a8cf67baebabf6340545409db873ec42553113775a7f74ef21
|
|
BLAKE2b-256 checksum How to use checksums |
a75bc73e511aa02b8cf818ce942b86c919ff09b9d818d829995f774f0263f281
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / django_simpletask5-0.2.9-py3-none-any.whl
| Download URL | django_simpletask5-0.2.9-py3-none-any.whl |
|---|---|
| Size | 128.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9e3071ac2ee6901eddbb174998e624e2c27f2b53dd26cbcce5f5e4133c5deaaa
|
|
BLAKE2b-256 checksum How to use checksums |
abbce8c8daf36ad8d48f7766d005691c0b719b7517ad984a12bae4ad08803916
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Security for django-simpletask5 0.2.9
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.