django-simpletask5 0.2.9__tar.gz → 0.2.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/PKG-INFO +184 -65
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/README.md +183 -64
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/admin.py +4 -2
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/defaults.py +12 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/worker_registry.py +68 -11
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/dashboards.py +101 -31
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/base.py +22 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/retry_timeout.py +17 -14
- django_simpletask5-0.2.11/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +6 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/management/commands/django_simpletask_executor.py +44 -57
- django_simpletask5-0.2.11/django_simpletask5/migrations/0015_taskexecution_idx_exec_status_created.py +19 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/models.py +87 -6
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5.egg-info/PKG-INFO +184 -65
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5.egg-info/SOURCES.txt +1 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/pyproject.toml +13 -1
- django_simpletask5-0.2.9/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/LICENSE +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/__init__.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/apps.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/__init__.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/cronjob_registry.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/executor_scanner.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/lock.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/logging_utils.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/message_queue.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/publisher.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/reclaim.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/security.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/core/signals.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/__init__.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/archive.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/bash_script.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/exceptions.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/loader.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/ping_pong.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/python_script.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/simple_request.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/executors/status_check.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/management/__init__.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/management/commands/__init__.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/management/commands/django_simpletask_archive.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/management/commands/django_simpletask_crontab.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/management/commands/django_simpletask_queues.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0001_initial.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0009_add_cronjob_app_label.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0010_taskexecution_id_alter_taskexecution_execution_id.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0011_taskexecutionstat_canceled_count.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0012_alter_taskexecution_status.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0013_alter_taskexecution_task_id.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/0014_cronjob_is_active_modified_by_user.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/migrations/__init__.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/services/__init__.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/services/archive.py +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/static/django_simpletask5/css/dashboard.css +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5/static/django_simpletask5/js/select2_executor.js +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5.egg-info/dependency_links.txt +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5.egg-info/requires.txt +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/django_simpletask5.egg-info/top_level.txt +0 -0
- {django_simpletask5-0.2.9 → django_simpletask5-0.2.11}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-simpletask5
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.11
|
|
4
4
|
Summary: Lightweight async task execution framework for Django
|
|
5
5
|
Author-email: rRR0VrFP <rrr0vrfp@qq.com>
|
|
6
6
|
Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
|
|
@@ -50,8 +50,9 @@ Dynamic: license-file
|
|
|
50
50
|
- **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
|
|
51
51
|
- **灵活的执行器映射** — 不同事件可绑定不同的执行器类
|
|
52
52
|
- **队列路由** — 不同事件可路由到不同优先级队列
|
|
53
|
-
- **重试与超时** —
|
|
53
|
+
- **重试与超时** — 进程崩溃后的孤儿任务由执行锁回收重投,支持 Pickup 超时检测;业务失败可直接标记终态
|
|
54
54
|
- **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
|
|
55
|
+
- **执行结果回调** — `on_success` / `on_failed` 钩子做通知、审计等收尾逻辑;业务失败时可在 `on_failed` 里用 `retrigger()` 另起新 execution 重投
|
|
55
56
|
- **加密字段** — 敏感数据自动加密存储
|
|
56
57
|
- **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
|
|
57
58
|
- **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
|
|
@@ -155,16 +156,26 @@ CACHES = {
|
|
|
155
156
|
'OPTIONS': {'CLIENT_CLASS': 'django_redis.client.DefaultClient'},
|
|
156
157
|
},
|
|
157
158
|
}
|
|
158
|
-
#
|
|
159
|
+
# 如需独立连接参数(仅作用于分布式锁):
|
|
159
160
|
# DJANGO_SIMPLETASK_LOCK_CONFIG = {
|
|
160
161
|
# 'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
|
|
161
162
|
# 'global_lock_engine_options': {'host': '127.0.0.1', 'port': 6379, 'db': 0,
|
|
162
163
|
# 'password': '<redis 密码,无需认证可省略>'},
|
|
163
164
|
# }
|
|
165
|
+
# Worker 注册中心(心跳/状态)默认复用 CACHES['default'] 的 Redis,
|
|
166
|
+
# 与锁配置解耦;如需独立实例可显式配置:
|
|
167
|
+
# DJANGO_SIMPLETASK_WORKER_REGISTRY_REDIS = {
|
|
168
|
+
# 'host': '127.0.0.1', 'port': 6379, 'db': 2,
|
|
169
|
+
# 'password': '<redis 密码,无需认证可省略>',
|
|
170
|
+
# }
|
|
171
|
+
# 若 CACHES['default'] 不是 Redis 缓存且未显式配置上面两项,注册中心会依次
|
|
172
|
+
# 回退到以下环境变量(db 固定为 0),最后兜底 127.0.0.1:6379/0:
|
|
173
|
+
# DJANGO_SIMPLETASK_REDIS_HOST = '127.0.0.1'
|
|
174
|
+
# DJANGO_SIMPLETASK_REDIS_PORT = 6379
|
|
175
|
+
# DJANGO_SIMPLETASK_REDIS_PASSWORD = None
|
|
164
176
|
# 注:globallock>=0.2.0 已取消按 timeout 自动释放的概念。锁的有效期由
|
|
165
177
|
# lockman/lock 作用域自行管理,作用域未结束前锁持续有效(内置看门狗自动续期),
|
|
166
|
-
# 锁在 `with`
|
|
167
|
-
# 与 DJANGO_SIMPLETASK_LOCK_RENEW_INTERVAL,请勿再配置这两项。
|
|
178
|
+
# 锁在 `with` 块退出或进程消亡时自动释放。
|
|
168
179
|
|
|
169
180
|
# ── Key 前缀与队列路由 ───────────────────────────────────────
|
|
170
181
|
DJANGO_SIMPLETASK_KEY_PREFIX = 'myproject' # Redis key 前缀,默认取 ROOT_URLCONF 首段
|
|
@@ -172,7 +183,8 @@ DJANGO_SIMPLETASK_DEFAULT_QUEUE = 'myproject:django-simpletask5:queue:default'
|
|
|
172
183
|
DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'myproject:django-simpletask5:queue:high_priority'
|
|
173
184
|
|
|
174
185
|
# ── 执行与重试 ───────────────────────────────────────────────
|
|
175
|
-
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
186
|
+
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3 # 进程崩溃后孤儿回收的最大重投次数
|
|
187
|
+
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIGGERS = 10 # on_failed 里 retrigger() 的重投链长度上限(0 禁止)
|
|
176
188
|
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 执行超时字段(秒,当前不主动中断)
|
|
177
189
|
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # Pickup 超时(秒)
|
|
178
190
|
|
|
@@ -228,13 +240,16 @@ django_app_requires_patch_all()
|
|
|
228
240
|
| `DJANGO_SIMPLETASK_RABBITMQ_HOST/PORT/USER/PASSWORD` | `127.0.0.1 / 5672 / guest / guest` | 类型为 `rabbitmq` 且未设 `BROKER_URL` 时生效。生产务必更换默认 `guest` 凭据 |
|
|
229
241
|
| `DJANGO_SIMPLETASK_REDIS_MQ_HOST/PORT/DB/PASSWORD` | `127.0.0.1 / 6379 / 0 / None` | 类型为 `redis` 时生效。建议与锁/缓存使用**不同 db**,避免 key 冲突与互相阻塞。Redis 需认证时设置 `PASSWORD` |
|
|
230
242
|
| `DJANGO_SIMPLETASK_BROKER_HEARTBEAT` | `60` | Kombu 发布连接池的 broker heartbeat(秒)。网络中间设备会断开空闲连接时建议保持开启 |
|
|
231
|
-
| `DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES` | `100` | AMQP 消息被 reject/requeue
|
|
243
|
+
| `DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES` | `100` | AMQP 消息被 reject/requeue 的次数**达到**该上限(`>=`)即 ack 丢弃,防止毒消息无限循环;memory/redis 传输无 `x-death` 时不生效 |
|
|
232
244
|
| `CACHES['default']` | — | 默认锁引擎 `DjangoRedisGlobalLock` 依赖它,**必须配置为 Redis 缓存** |
|
|
233
|
-
| `DJANGO_SIMPLETASK_LOCK_CONFIG` | `None`(默认复用缓存锁) | 需要独立连接参数时显式配置(`global_lock_engine_options` 支持 `host/port/db/password
|
|
234
|
-
| `
|
|
245
|
+
| `DJANGO_SIMPLETASK_LOCK_CONFIG` | `None`(默认复用缓存锁) | 需要独立连接参数时显式配置(`global_lock_engine_options` 支持 `host/port/db/password`)。**仅用于分布式锁**,与 Worker 注册中心无关。globallock>=0.2.0 起锁有效期由 lockman/lock 作用域管理(内置看门狗自动续期) |
|
|
246
|
+
| `DJANGO_SIMPLETASK_WORKER_REGISTRY_REDIS` | `None`(复用 `CACHES['default']`) | Worker 注册中心(心跳/状态观测)专用 Redis,`host/port/db/password`。**与锁配置解耦**;未配置时复用 `CACHES['default']` 的 Redis 连接(默认锁引擎亦复用该缓存,故通常无需单独配置),若缓存非 Redis 则回退环境变量或 `127.0.0.1:6379/0` |
|
|
247
|
+
| `DJANGO_SIMPLETASK_REDIS_HOST/PORT/PASSWORD` | `127.0.0.1 / 6379 / None` | Worker 注册中心连接参数的**环境变量兜底**(`os.environ`,非 settings),优先级低于 `WORKER_REGISTRY_REDIS` 与 `CACHES['default']`。仅 `HOST/PORT/PASSWORD` 三项,**db 固定为 `0`**;最终兜底 `127.0.0.1:6379/0` |
|
|
248
|
+
| `DJANGO_SIMPLETASK_KEY_PREFIX` | 自动取 `ROOT_URLCONF` 首段(未设置时回退 `default`) | 生产建议**显式指定**,多项目共用 Redis 时避免 key 冲突 |
|
|
235
249
|
| `DJANGO_SIMPLETASK_DEFAULT_QUEUE` | `{prefix}:...:queue:default` | 默认队列名。多环境(dev/staging/prod)建议加环境后缀隔离 |
|
|
236
250
|
| `DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE` | `{prefix}:...:queue:high_priority` | 高优先级队列,**需单独启动 Worker 消费** |
|
|
237
|
-
| `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES` | `3` |
|
|
251
|
+
| `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES` | `3` | 默认最大重试次数。仅用于进程崩溃后的孤儿回收重投(`core.reclaim`);业务侧异常不会消耗该次数 |
|
|
252
|
+
| `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIGGERS` | `10` | 跨 execution 重投链长度上限(业务在 `on_failed` 里调用 `execution.retrigger()` 时生效)。设为 `0` 表示禁止重投 |
|
|
238
253
|
| `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS` | `600` | 写入 `TaskExecution.timeout_seconds`,并用于计算 running 时的 `expire_time`。当前版本不会因此主动中断 running 任务;严格超时应由执行器自行实现 |
|
|
239
254
|
| `DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS` | `3600` | Pickup 超时(等待 Worker 消费的上限),**通常大于执行超时**,因为排队等待时间不可控 |
|
|
240
255
|
| `DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY` | `{'max_retries': 2, 'interval_start': 0.2, 'interval_step': 0.3, 'interval_max': 1.0}` | Kombu Producer 内层重试策略。应用层还会做最多 3 次发布尝试 |
|
|
@@ -244,7 +259,6 @@ django_app_requires_patch_all()
|
|
|
244
259
|
| `DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT` | `30` | **每个** worker 线程的优雅停机等待时长;总等待时间可能为 worker 数乘以该值 |
|
|
245
260
|
| `DJANGO_SIMPLETASK_LOG_INTERVAL` | `300` | Worker 注册失败、Crontab 错误等日志的节流间隔。日志量大时可调大 |
|
|
246
261
|
| `DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL` | `60` | 发布、重投、孤儿回收失败日志的节流间隔(秒) |
|
|
247
|
-
| `DJANGO_SIMPLETASK_ARCHIVE_PATH` | `django_simpletask5_archives` | **当前版本未读取,仅保留兼容**。归档文件实际写入 `MEDIA_ROOT/django_simpletask5_archives/%Y/%m/` |
|
|
248
262
|
| `DJANGO_SIMPLETASK_ARCHIVE_SALT` | `django-simpletask5-archive` | 归档加密盐值,**生产必须修改**(结合 `SECRET_KEY` 派生 AES 密钥) |
|
|
249
263
|
| `DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS` | `7` | 归档保留天数,按合规要求调整 |
|
|
250
264
|
| `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC` | `True` | 自动把代码注册的 Cron 同步到数据库;只想手工管理时设为 `False` |
|
|
@@ -255,13 +269,13 @@ django_app_requires_patch_all()
|
|
|
255
269
|
| `DJANGO_SIMPLETASK_ERROR_MSG_PASSWORD` | `None` | `error_message` 字段加密密码,同上 |
|
|
256
270
|
| `DJANGO_SIMPLETASK_CONTEXT_PASSWORD` | `None` | `context` 字段加密密码,同上 |
|
|
257
271
|
|
|
258
|
-
> 注意:`DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS` 设为 `True` 后,还需为用户/组授予 `django_simpletask5 | Cron job | Can use script executors` 权限,非超级管理员无法创建 Python/Shell 脚本执行器类型的定时任务。
|
|
272
|
+
> 注意:`DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS` 设为 `True` 后,还需为用户/组授予 `django_simpletask5 | Cron job | Can use script executors (Python/Shell)` 权限,非超级管理员无法创建 Python/Shell 脚本执行器类型的定时任务。
|
|
259
273
|
|
|
260
274
|
> 发布可靠性说明:应用层 `_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()` 保留异常上抛语义,供需要自行处理错误的调用方使用。
|
|
261
275
|
|
|
262
276
|
## 快速开始(从零到一集成)
|
|
263
277
|
|
|
264
|
-
本框架的集成分三步:**定义任务模型 → 编写执行器 → 启动 Worker**。完整可运行的参考实现见仓库中的 `django_simpletask5_example` 应用,包含 `OrderTask`
|
|
278
|
+
本框架的集成分三步:**定义任务模型 → 编写执行器 → 启动 Worker**。完整可运行的参考实现见仓库中的 `django_simpletask5_example` 应用,包含 `OrderTask` 模型、创建/更新/删除三个执行器及内置 Cron 任务。
|
|
265
279
|
|
|
266
280
|
### 1. 定义任务模型
|
|
267
281
|
|
|
@@ -507,6 +521,8 @@ OrderTask.objects.create(..., skip_publish=True) # 方式四:构造参数(
|
|
|
507
521
|
| `get_tasks(execution)` | 返回可迭代对象,默认 `yield None`(单任务) |
|
|
508
522
|
| `get_tasks_count(execution)` | 任务总数(用于进度展示),默认 `1` |
|
|
509
523
|
| `reject(execution, reason='')` | 拒绝任务:状态复位 `pending`、清空 `started_at`、刷新 `expire_time` |
|
|
524
|
+
| `on_success(execution, result=None)` | 执行成功后的收尾回调(默认空实现),见「执行结果回调」 |
|
|
525
|
+
| `on_failed(execution, error=None)` | 执行失败后的收尾回调(默认空实现),见「执行结果回调」 |
|
|
510
526
|
| `timeout_seconds` / `pickup_timeout_seconds` | 类属性。`pickup_timeout_seconds` 用于自动超时判定;`timeout_seconds` 当前仅记录预期执行上限,不主动中断任务 |
|
|
511
527
|
| `get_parameter_schema()` | 声明执行器参数,用于 Admin 参数配置界面(默认空) |
|
|
512
528
|
|
|
@@ -553,8 +569,125 @@ class BatchSyncExecutor(BaseExecutor):
|
|
|
553
569
|
|---|---|
|
|
554
570
|
| 无异常 | 状态 `success`,`result` 保存返回值 |
|
|
555
571
|
| `RejectException` | 拒绝回退:状态复位 `pending`,消息重新入队,**不消耗**重试次数 |
|
|
556
|
-
|
|
|
557
|
-
|
|
572
|
+
| 其他异常(业务侧异常) | 状态 `failed`,`error_message` 记录异常信息,**不重试** |
|
|
573
|
+
|
|
574
|
+
> 语义区分:**重试/恢复只用于「执行器进程崩溃」的场景**,由 crontab 基于执行锁过期回收(见 `core.reclaim`)。执行器自身抛出的异常(业务逻辑异常、执行器加载失败等)一律视为最终失败,直接标记 `failed`,不会消耗重试次数或重新入队,避免确定性错误被反复重投。`retry_count`/`max_retries` 仅用于进程崩溃后的回收重投。
|
|
575
|
+
|
|
576
|
+
### 执行结果回调(on_success / on_failed)
|
|
577
|
+
|
|
578
|
+
`BaseExecutor` 提供两个可选钩子,默认都是空实现。框架在把 execution 的终态落库**之后**调用它们,用于放置「主执行逻辑之外」的收尾工作——发通知、写审计、刷新缓存、触发下游,或决定是否重投。
|
|
579
|
+
|
|
580
|
+
```python
|
|
581
|
+
import json
|
|
582
|
+
import logging
|
|
583
|
+
|
|
584
|
+
from django_simpletask5.executors.base import BaseExecutor
|
|
585
|
+
from django_simpletask5.models import TaskExecution
|
|
586
|
+
|
|
587
|
+
logger = logging.getLogger(__name__)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class OrderCreateExecutor(BaseExecutor):
|
|
591
|
+
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
592
|
+
# 主执行逻辑:返回的字符串会写入 execution.result
|
|
593
|
+
data = execution.get_context_dict().get('data', {})
|
|
594
|
+
return json.dumps(self._sync_to_erp(data))
|
|
595
|
+
|
|
596
|
+
def on_success(self, execution: TaskExecution, result=None) -> None:
|
|
597
|
+
# 状态已是 success 且已落库;result 即 execute() 的返回值
|
|
598
|
+
notify_success(execution.task_id, result)
|
|
599
|
+
logger.info('订单同步成功 execution=%s', execution.execution_id)
|
|
600
|
+
|
|
601
|
+
def on_failed(self, execution: TaskExecution, error=None) -> None:
|
|
602
|
+
# 状态已是 failed 且已落库;error 为捕获到的异常对象
|
|
603
|
+
alert(execution.task_id, error)
|
|
604
|
+
logger.warning('订单同步失败 execution=%s: %s', execution.execution_id, error)
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**调用时机与参数**
|
|
608
|
+
|
|
609
|
+
| 钩子 | 触发时机 | 第二个参数 |
|
|
610
|
+
|---|---|---|
|
|
611
|
+
| `on_success(execution, result=None)` | 执行器 `execute()` 正常返回,`success` 落库之后 | `result` = `execute()` 的返回值 |
|
|
612
|
+
| `on_failed(execution, error=None)` | 执行器 `execute()` 抛出**业务异常**,`failed` 落库之后 | `error` = 捕获到的异常对象 |
|
|
613
|
+
|
|
614
|
+
**关键约定**
|
|
615
|
+
|
|
616
|
+
- **触发范围**:两个钩子只在 Worker 持有执行锁、进入执行器调用的 `try` 块内被触发。以下转入 `failed` 的路径**不会**触发 `on_failed`:发布失败标记(`safe_publish_message`)、孤儿回收重试次数耗尽(`core.reclaim`)、`RejectException` 拒绝回退、数据库瞬时错误重入队。因此**不能**依赖 `on_failed` 兜底所有失败——它只覆盖「执行器自身抛出业务异常」这一条路径。
|
|
617
|
+
- **异常隔离**:回调内抛出的异常会被框架吞掉并记录日志,**不影响已落库的主执行结果,也不会触发消息重投**。收尾逻辑应自行保证幂等。
|
|
618
|
+
- **不做重投**:框架**不会**因为 `on_failed` 而重投;需要重投时必须在回调内显式调用 `execution.retrigger()`(见下节)。
|
|
619
|
+
|
|
620
|
+
### 失败重投:retrigger()
|
|
621
|
+
|
|
622
|
+
业务在 `on_failed` 里调用 `execution.retrigger()`,会基于当前 execution 创建一条**全新的** `TaskExecution`(新的 `execution_id`、`retry_count=0`)并发布消息;原记录保持其终态作为历史留痕。这与进程崩溃后的 `retry` 语义正交——前者是业务主动决策,后者是框架对进程异常的兜底回收。
|
|
623
|
+
|
|
624
|
+
**完整示例:只对临时性错误重投,并按重投次数调整策略**
|
|
625
|
+
|
|
626
|
+
```python
|
|
627
|
+
import json
|
|
628
|
+
import logging
|
|
629
|
+
|
|
630
|
+
from django_simpletask5.executors.base import BaseExecutor
|
|
631
|
+
from django_simpletask5.models import TaskExecution
|
|
632
|
+
|
|
633
|
+
logger = logging.getLogger(__name__)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
class TransientSyncError(Exception):
|
|
637
|
+
"""临时性错误(网络抖动、下游限流等),可重投。"""
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
class OrderCreateExecutor(BaseExecutor):
|
|
641
|
+
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
642
|
+
context = execution.get_context_dict()
|
|
643
|
+
# 上一轮重投透传下来的失败原因,可用于日志或降级
|
|
644
|
+
last_error = context.get('last_error')
|
|
645
|
+
if last_error:
|
|
646
|
+
logger.info('重投执行,上次错误:%s', last_error)
|
|
647
|
+
return json.dumps(self._sync_to_erp(context.get('data', {})))
|
|
648
|
+
|
|
649
|
+
def on_failed(self, execution: TaskExecution, error=None) -> None:
|
|
650
|
+
# 仅对临时性错误重投;确定性错误(如参数非法)不重投
|
|
651
|
+
if not isinstance(error, TransientSyncError):
|
|
652
|
+
logger.warning('非临时性错误,不重投:%s', error)
|
|
653
|
+
return
|
|
654
|
+
|
|
655
|
+
new_execution = execution.retrigger(
|
|
656
|
+
extra_context={'last_error': str(error)},
|
|
657
|
+
)
|
|
658
|
+
if new_execution is None:
|
|
659
|
+
# 已达重投上限:放弃重投,交由告警/人工处理
|
|
660
|
+
logger.error('重投链已达上限,放弃重投 execution=%s', execution.execution_id)
|
|
661
|
+
alert(execution.task_id, error)
|
|
662
|
+
return
|
|
663
|
+
|
|
664
|
+
logger.info(
|
|
665
|
+
'已重投:%s -> %s(第 %s 次)',
|
|
666
|
+
execution.execution_id,
|
|
667
|
+
new_execution.execution_id,
|
|
668
|
+
new_execution.get_context_dict().get('_retrigger_count'),
|
|
669
|
+
)
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
**`retrigger(extra_context=None)` 行为说明**
|
|
673
|
+
|
|
674
|
+
- **参数**:`extra_context` 为可选 dict,会**合并**进新 execution 的上下文,供下次执行读取(如上次失败原因、调整后的重试参数)。**勿传入 `_retrigger_count` / `_retriggered_from` 等保留键**——它们会在合并时覆盖框架写入的防死循环标记。
|
|
675
|
+
- **返回值**:返回新建的 `TaskExecution`;达到重投上限时返回 `None` 且不创建记录,**必须自行判空**。
|
|
676
|
+
- **字段复制范围**:新 execution 仅复制 `task_id / task_model / trigger_event / executor_class / timeout_seconds / pickup_timeout_seconds / max_retries / context`;`result`、`error_message` 与进度计数**不会**带过去(原记录保留这些历史)。
|
|
677
|
+
- **队列**:新 execution 固定发布到 `DJANGO_SIMPLETASK_DEFAULT_QUEUE`,**不会**沿用原事件的自定义队列路由(如 high_priority)。
|
|
678
|
+
- **上下文标记**:新 execution 的上下文会自动带上 `_retrigger_count`(重投链累计重投次数)与 `_retriggered_from`(来源 `execution_id`),执行器可据此调整策略。
|
|
679
|
+
- **发布失败**:若新消息发布失败,`safe_publish_message()` 会把新 execution 标记为 `failed`,`retrigger()` 仍返回该记录(非 `None`)。
|
|
680
|
+
|
|
681
|
+
**防死循环**:重投链长度由 `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIGGERS`(默认 `10`)限制,计数以 `context['_retrigger_count']` 在同一条链上累加。达到上限时 `retrigger()` 返回 `None` 且不创建新记录,从结构上封死 `a -> a` 之类的无限循环。设为 `0` 表示禁止重投。
|
|
682
|
+
|
|
683
|
+
**retrigger 与 retry 的区别**
|
|
684
|
+
|
|
685
|
+
| | `retrigger()`(业务重投) | `retry`(框架回收) |
|
|
686
|
+
|---|---|---|
|
|
687
|
+
| 触发者 | 业务在 `on_failed` 回调中主动调用 | 框架 `core.reclaim` 检测到进程崩溃/锁丢失 |
|
|
688
|
+
| 记录 | 新建一条 execution(新 `execution_id`、`retry_count=0`) | 复用原 execution,`retry_count + 1` |
|
|
689
|
+
| 原记录 | 保持 `failed` 终态留痕 | 状态变为 `retry` 后重新入队 |
|
|
690
|
+
| 计数上限 | `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIGGERS` | `max_retries` |
|
|
558
691
|
|
|
559
692
|
### 拒绝回退
|
|
560
693
|
|
|
@@ -570,7 +703,7 @@ class MyExecutor(BaseExecutor):
|
|
|
570
703
|
```
|
|
571
704
|
|
|
572
705
|
框架捕获后:
|
|
573
|
-
-
|
|
706
|
+
- Worker 返回「需重新入队」信号,由消息层调用 `message.reject(requeue=True)` 将消息放回队列尾部,下次重新调度
|
|
574
707
|
- `TaskExecution` 状态恢复为 `pending`,`started_at` 清空,等待下次 Worker 消费
|
|
575
708
|
- **不消耗**重试次数
|
|
576
709
|
- 刷新 `expire_time`(使用 Pickup 超时),防止被 `StatusCheckExecutor` 误回收
|
|
@@ -601,7 +734,7 @@ class OrderCreateExecutor(BaseExecutor):
|
|
|
601
734
|
|
|
602
735
|
`TaskExecution.task_id` 存的是业务对象主键的字符串表示(`CharField`)。需要反查业务对象时(`execution.get_task()` / `execution.confirm_delete()`),框架先通过 `task_model` 解析出模型类,再调用该类的 `get_task_instance(task_id)` 完成查询。
|
|
603
736
|
|
|
604
|
-
默认实现在 `Task` 基类中按主键 `pk`
|
|
737
|
+
默认实现在 `Task` 基类中按主键 `pk` 查询,并自动兼容常见主键类型:
|
|
605
738
|
|
|
606
739
|
- `AutoField` / `BigAutoField` / `SmallAutoField` — 字符串转为 `int`
|
|
607
740
|
- `UUIDField` — 字符串转为 `uuid.UUID`
|
|
@@ -632,8 +765,7 @@ class OrderTask(Task):
|
|
|
632
765
|
```
|
|
633
766
|
pending ──(Worker 消费)──▶ running ──(执行成功)──▶ success
|
|
634
767
|
│ │
|
|
635
|
-
│ ├─(
|
|
636
|
-
│ ├─(异常且重试耗尽)──────▶ failed
|
|
768
|
+
│ ├─(业务侧异常)─────────▶ failed ──(on_failed 里 retrigger)──▶ 新 execution(pending)
|
|
637
769
|
│ └─(执行器异常退出/锁丢失)─▶ retry 或 failed(由 reclaim 回收)
|
|
638
770
|
├─(pickup 超时,从未被领取)──▶ timeout ──(RetryTimeout)──▶ retry
|
|
639
771
|
├─(用户取消)────────────────▶ canceled(终态)
|
|
@@ -642,11 +774,11 @@ pending ──(Worker 消费)──▶ running ──(执行成功)──▶ suc
|
|
|
642
774
|
|
|
643
775
|
| 状态 | 含义 |
|
|
644
776
|
|---|---|
|
|
645
|
-
| `pending` | 已发布,等待 Worker
|
|
777
|
+
| `pending` | 已发布,等待 Worker 消费;拒绝回退、`retrigger()` 新建的 execution 也处于该状态 |
|
|
646
778
|
| `running` | 已获取执行锁,正在执行 |
|
|
647
779
|
| `success` | 执行成功 |
|
|
648
|
-
| `failed` |
|
|
649
|
-
| `retry` |
|
|
780
|
+
| `failed` | 业务侧异常、发布最终失败、或进程崩溃孤儿回收时已无重试次数。**终态**;仅「业务侧异常」这一条路径会触发 `on_failed` 回调,业务可在此 `retrigger()` 另起一条新 execution |
|
|
781
|
+
| `retry` | 进程崩溃后孤儿回收、超时后等待重新投递,消息已重新入队 |
|
|
650
782
|
| `timeout` | **pending** 超过 pickup 期限仍未被领取;running 不会因 `expire_time` 自动变成 timeout |
|
|
651
783
|
| `canceled` | 被用户主动取消(终态,Worker 不会执行) |
|
|
652
784
|
|
|
@@ -666,7 +798,8 @@ TaskExecution.objects.filter(
|
|
|
666
798
|
|
|
667
799
|
### 重试与超时
|
|
668
800
|
|
|
669
|
-
- **重试**:`max_retries`(默认 3)控制最大重试次数,`retry_count`
|
|
801
|
+
- **重试**:`max_retries`(默认 3)控制最大重试次数,`retry_count` 累计已重试次数。重试**只发生在执行器进程崩溃后的孤儿回收**(`core.reclaim`)场景;执行器自身抛出的业务异常直接标记 `failed`,不消耗重试次数
|
|
802
|
+
- **失败重投**(`retrigger()`):与上面的进程崩溃重试语义正交。业务在 `on_failed` 回调里调用 `execution.retrigger()` 可另起一条**全新** execution(新 `execution_id`、`retry_count=0`),原记录保持终态留痕;重投链长度受 `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIGGERS` 限制。详见「失败重投:retrigger()」
|
|
670
803
|
- **Pickup 超时**(`pickup_timeout_seconds`):**唯一由框架自动判定的超时**。`pending` 超过 `expire_time` 后由 `StatusCheckExecutor` 标记为 `timeout`,也用于拒绝回退后的重新调度保护;Pickup 超时不消耗重试次数
|
|
671
804
|
- **执行超时字段**(`timeout_seconds`):写入 `TaskExecution.timeout_seconds`,并在任务进入 `running` 时用于计算 `expire_time`。当前版本**不会**因该时间到达而主动中断 running 任务;如果执行器 hang 住,需在执行器内部实现超时/取消机制
|
|
672
805
|
- **超时检测与恢复**:`StatusCheckExecutor` 每分钟把超过 pickup 期限的 `pending` 记录标记为 `timeout`;`RetryTimeoutExecutor` 每分钟把 `timeout` 记录重新入队,实际运行过的记录消耗一次重试次数。running 孤儿任务由 crontab 基于执行锁过期回收,而不是按 `expire_time` 判超时
|
|
@@ -744,26 +877,6 @@ python manage.py django_simpletask_sync_cronjobs
|
|
|
744
877
|
| `ArchiveExecutor` | 归档前一天的终态执行并生成统计(内置 Cron 每天 2:00) |
|
|
745
878
|
| `PurgeExpiredExecutor` | 删除超过 `ARCHIVE_RETENTION_DAYS` 的终态执行记录(内置 Cron 每天 3:00;不删除归档文件) |
|
|
746
879
|
|
|
747
|
-
## 开发与测试
|
|
748
|
-
|
|
749
|
-
项目提供 `start-test-services.sh` 一键启动测试所需的 Redis 与 RabbitMQ 服务。脚本优先检测本地已拉取的镜像(避免依赖 `docker.io` 上游镜像),也可通过 `DJANGO_SIMPLETASK_REDIS_IMAGE` / `DJANGO_SIMPLETASK_RABBITMQ_IMAGE` 环境变量指定,默认使用 6379/5672 端口(被占用时自动选取空闲端口):
|
|
750
|
-
|
|
751
|
-
```bash
|
|
752
|
-
./start-test-services.sh start # 启动服务并等待就绪
|
|
753
|
-
./start-test-services.sh status # 查看容器与端口状态
|
|
754
|
-
./start-test-services.sh stop # 停止并清理容器
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
运行测试:
|
|
758
|
-
|
|
759
|
-
```bash
|
|
760
|
-
python3 -m pytest django_simpletask5_example/tests
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
测试套件覆盖 Redis、DB、MQ 连接中断后的自动恢复场景,详见 `django_simpletask5_example/tests/test_resilience_recovery.py`。
|
|
764
|
-
|
|
765
|
-
**测试输出约定**:单元测试中对预期会触发的错误(如无效 UUID 校验、毒消息丢弃、消费者断连、执行器加载失败等)所输出的错误日志,必须在测试内屏蔽,避免污染测试输出。统一使用 `django_simpletask5_example.tests.suppress_expected_errors` 上下文管理器(整体禁用日志)或 `patch.object(..., 'logger')`(断言同时校验日志)进行屏蔽。
|
|
766
|
-
|
|
767
880
|
## 架构
|
|
768
881
|
|
|
769
882
|
```
|
|
@@ -771,34 +884,47 @@ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息
|
|
|
771
884
|
↓
|
|
772
885
|
Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
|
|
773
886
|
↓
|
|
774
|
-
|
|
887
|
+
success / failed 落库 → 调用 on_success / on_failed 回调
|
|
888
|
+
↓
|
|
889
|
+
拒绝时重新入队;进程崩溃由 reclaim 基于锁过期回收;业务失败可 retrigger 重投;终态记录归档
|
|
775
890
|
```
|
|
776
891
|
|
|
777
892
|
## Releases
|
|
778
893
|
|
|
894
|
+
### 0.2.11
|
|
895
|
+
|
|
896
|
+
- **执行结果回调钩子** — `BaseExecutor` 新增 `on_success(execution, result=None)` / `on_failed(execution, error=None)` 钩子(默认空实现)。框架在 `success` / `failed` 状态落库后调用,回调内的异常被吞掉并记录日志,不影响主执行结果、不触发消息重投
|
|
897
|
+
- **失败重投 `retrigger()`** — `TaskExecution.retrigger(extra_context=None)` 基于当前记录创建全新 execution(新 `execution_id`、`retry_count=0`)并发布消息,原记录保持终态留痕。重投链长度由新配置 `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIGGERS`(默认 `10`)限制,计数以 `context['_retrigger_count']` 累加,达到上限返回 `None` 且不创建,从结构上防止 `a -> a` 死循环
|
|
898
|
+
- **业务异常语义澄清** — 执行器业务侧异常一律直接标记 `failed`、不重试/不重新入队;`retry_count`/`max_retries` 仅用于「执行器进程崩溃」的孤儿回收(`core.reclaim`)。`RejectException` 语义保持不变
|
|
899
|
+
- **Worker 注册中心 Redis 与锁配置解耦** — 新增 `DJANGO_SIMPLETASK_WORKER_REGISTRY_REDIS` 显式配置 Worker 注册中心(心跳/状态观测)的 Redis 连接,与 `DJANGO_SIMPLETASK_LOCK_CONFIG` 完全解耦。未配置时按优先级回退:`CACHES['default']` 的 Redis 连接 → 环境变量 → `127.0.0.1:6379/0`,默认零配置下仍复用缓存 Redis
|
|
900
|
+
|
|
901
|
+
### 0.2.10
|
|
902
|
+
|
|
903
|
+
- **修复 MySQL/InnoDB 并发写入死锁** — 后台批量改状态原使用 `WHERE execution_id IN (...)`、`WHERE status = 'pending'` 等**二级索引范围更新**,会与前台 `Task` 子类 `create()` 触发的 `TaskExecution` 插入在二级索引间隙上相互阻塞,在高并发下偶发 `OperationalError (1213)` 死锁。现改为**先查出主键、再按主键更新**(`WHERE id IN (...)`),不触碰二级索引间隙,从源头消除死锁,无需重试、不改变原有同事务语义
|
|
904
|
+
- `django_simpletask5/executors/retry_timeout.py`:领取超时 / 执行超时的批量改状态改为按主键更新
|
|
905
|
+
- `django_simpletask5/admin.py`:`cancel_selected` 批量取消改为按主键更新
|
|
906
|
+
- **修复 MySQL 下 `bulk_create` 触发任务时 `task_id` 为空** — 部分数据库后端(如 MySQL)的 `bulk_create` 不回填自增主键,导致批量创建时生成的 `TaskExecution.task_id` 落成 `None`,业务对象无法反查。现按 `skip_publish` 分流:标记跳过发布的实例仍走高性能批量插入,需要触发任务的实例改为逐条保存,由信号在拿到真实主键后创建并发布 execution。返回值语义保持不变:仍返回传入的同一批实例并保持原顺序
|
|
907
|
+
- **修复示例项目 `OrderTask` 迁移的主键顺序** — 修正 `0002` 迁移先删旧主键再新增自增主键,解决 MySQL 执行时 `1068 Multiple primary key defined`
|
|
908
|
+
- **Dashboard 查询优化** — 卡片当日指标、近 30 天状态分布改用走索引的自然日范围查询(`created_at__gte/__lt`)替代 `created_at__date` 全表扫描;每日执行图表由逐日 60 次 `count` 合并为单次 `TruncDate` 分组聚合。仪表盘整体查询数降至常量级,不再随天数增长。新增 `TaskExecution` 的 `(status, created_at)` 联合索引(迁移 `0015`)支撑状态分布查询
|
|
909
|
+
- **Dashboard Worker 表增强** — Active Workers 表新增“运行时长”与“最后心跳”两列;心跳距今超过 Worker 注册 TTL 阈值(约 60%)时以 `⚠` 标记,提示该 Worker 可能即将失联;时间戳缺失或非法时降级为 `-`,不影响其余展示
|
|
910
|
+
|
|
779
911
|
### 0.2.9
|
|
780
912
|
|
|
781
|
-
- **启用状态所有权独立跟踪** — 新增 `CronJob.is_active_modified_by_user`
|
|
913
|
+
- **启用状态所有权独立跟踪** — 新增 `CronJob.is_active_modified_by_user` 字段,将“启用/暂停状态”与“定义字段”的所有权彻底分离。用户手动启停(快捷按钮 / 批量操作 / 编辑表单)后置该标记,自动同步(`sync_cronjobs`)不再覆盖 `is_active`;未手动改过的任务仍按代码注册值同步。暂停只锁 `is_active`,`cron_expression` 等定义字段照常同步
|
|
782
914
|
- **显式释放所有权** — 该标记不会自动清除;需要让任务重新跟随代码时,使用管理后台“从代码定义重置”(`reset_from_code`),或在编辑页取消勾选“保留手动启用状态”
|
|
783
915
|
- **保留列表分页/过滤条件** — 修复暂停/运行按钮提交后重定向丢失查询串的问题:按钮表单 `action` 及视图重定向均携带当前 changelist 的 `GET` 参数,操作后刷新仍停留在原分页、过滤器与搜索条件
|
|
784
916
|
- **消息发布可靠性增强** — Kombu 发布复用连接池并支持 broker heartbeat;`publish()` 返回 `queue` / `message_count` / `consumer_count` 队列统计;新增可配置发布重试策略和递增重试延迟;发布异常时自动丢弃损坏连接;新增公开的 `publish_message()` 与兜底的 `safe_publish_message()`,最终发布失败会将 execution 标记为 `failed`
|
|
785
917
|
- **发布日志节流** — 发布、重试投递、孤儿任务回收和 CronJob 触发失败统一接入日志节流,避免 broker/Redis 长时间不可用时刷屏;重投日志同时记录队列积压与消费者数量
|
|
786
|
-
- **Worker 与回收健壮性** — Worker 注册表 Redis 客户端改为公开 `get_redis_client()`;修正 Worker 清理 key 的 glob
|
|
787
|
-
- **日志初始化修复** —
|
|
918
|
+
- **Worker 与回收健壮性** — Worker 注册表 Redis 客户端改为公开 `get_redis_client()`;修正 Worker 清理 key 的 glob;回收扫描前后关闭旧数据库连接,减少长时间运行后的连接失效
|
|
919
|
+
- **日志初始化修复** — 不再把 `FileHandler` 误判为控制台 handler,避免已有文件日志时跳过控制台输出
|
|
788
920
|
- **CronJob 同步健壮性** — `sync_cronjobs(force=True)` 支持手动同步命令绕过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC`;单条 CronJob 创建失败不再中断其它任务同步;`django_simpletask_sync_cronjobs` 命令改为强制同步
|
|
789
|
-
-
|
|
790
|
-
-
|
|
791
|
-
- **注释与文档校准** — 为整个 `django_simpletask5` 生产包补充中文模块/类/函数/分支注释,并修正归档路径、归档清理、`running/timeout` 状态流转、Admin 只读性、脚本白名单、连接池 heartbeat、优雅退出等待时间等与实际行为不一致的说明
|
|
792
|
-
- **迁移与翻译** — 新增迁移 `django_simpletask5.0014_cronjob_is_active_modified_by_user`,同步更新简体中文翻译与迁移中的字段 `help_text`
|
|
793
|
-
- **测试** — 新增 `test_logging_utils.py`;扩充消息发布、消息队列、CronJob 同步、Admin 启停、Reclaim、Worker 恢复、Redis 锁与集成测试,覆盖最终发布失败、队列积压统计、`force` 同步、启停所有权、查询串保留、空锁配置和 Worker 清理 glob 等场景
|
|
794
|
-
- **环境要求** — 代码使用 `X | None` 联合类型,最低 Python 版本明确为 3.10;同时将 Django 最低版本明确为 4.2,并补充 `Django>=4.2` 依赖声明
|
|
795
|
-
- **版本** — `pyproject.toml` 版本更新为 `0.2.9`
|
|
921
|
+
- **可观测性入口公开** — Dashboard 使用的 Worker 统计入口改为公开的 `get_worker_stats()`;消费者线程心跳改为读取 `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL`
|
|
922
|
+
- **环境要求** — 最低 Python 版本明确为 3.10;同时将 Django 最低版本明确为 4.2,并补充 `Django>=4.2` 依赖声明
|
|
796
923
|
|
|
797
924
|
### 0.2.8
|
|
798
925
|
|
|
799
926
|
- **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
|
|
800
|
-
- **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk`
|
|
801
|
-
- **新增迁移** — `django_simpletask5.0013_alter_taskexecution_task_id`(字段改 CharField)、`django_simpletask5_example.0003_remove_ordertask_task_id`
|
|
927
|
+
- **业务对象反查下沉到模型层** — 新增 `Task.get_task_instance(task_id)` 类方法作为默认反查实现(按 `pk` 查询,内置兼容 `int` / `UUID` / 字符串主键);`TaskExecution.get_task()` / `confirm_delete()` 改为解析 `task_model` 后委托给该方法。业务模型使用自定义主键或自然键时重载 `get_task_instance()` 即可
|
|
802
928
|
- **破坏性变更**: 依赖 `TaskExecution.task_id` 为 UUID 类型的外部消费方需适配为业务主键字符串;业务 `Task` 子类若曾使用内置 `task_id` 字段需移除
|
|
803
929
|
|
|
804
930
|
### 0.2.7
|
|
@@ -806,16 +932,12 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
806
932
|
- **运行中任务回收机制重构** — 取消原先按 `expire_time` 将 running 直接判为超时的语义;`StatusCheckExecutor` 仅兜底回收长期未被 Worker 获取的 pending。新增 `core/reclaim`,由 crontab 每 ~5 秒基于执行锁探测运行中任务:锁已过期(执行器异常退出)即快速重投或直接置失败,降低孤儿任务滞留时间
|
|
807
933
|
- **重试不再搁浅任务** — 失败重试先落库 `retry` 再发布重试消息,发布失败则 requeue 当前消息,杜绝任务永久停在无人处理的 `retry` 状态;KombuQueue 依据 AMQP `x-death` 上限丢弃毒消息,避免无限 requeue 造成的队头阻塞与空转
|
|
808
934
|
- **消费者重连提示** — Worker 断连重连成功后输出 `Consumer connection restored` 恢复日志,便于运维确认消费通道已恢复
|
|
809
|
-
- **安全与健壮性加固** — broker 账号/密码按百分号编码、脚本白名单按路径段边界匹配、signals
|
|
810
|
-
- **新增测试约定** — 单元测试中对预期触发(如无效 UUID 校验、毒消息丢弃、消费者断连、执行器加载失败等)产生的错误日志必须屏蔽输出,统一使用 `django_simpletask5_example.tests.suppress_expected_errors` 或替换 logger,保证测试输出干净可读;本次同步清理全部此类预期错误日志
|
|
935
|
+
- **安全与健壮性加固** — broker 账号/密码按百分号编码、脚本白名单按路径段边界匹配、signals 快照按实例隔离
|
|
811
936
|
|
|
812
937
|
### 0.2.6
|
|
813
938
|
|
|
814
939
|
- **新增 `canceled` 状态** — 取消已发布但未执行的任务改为独立的终态 `canceled`(原复用 `failed`),Worker 消费到已取消消息时直接跳过;Admin 取消操作、归档/清理/统计(新增 `canceled_count`)、Dashboard 状态图均纳入该状态,取消与执行失败可清晰区分
|
|
815
940
|
- **WorkerSupervisor 守护线程** — 统一的 Worker 线程保活机制,周期性心跳并自动重启异常退出的 Worker 线程,保持目标 Worker 数量,提升长时间运行稳定性
|
|
816
|
-
- **韧性/恢复测试套件** — 新增 `test_resilience_recovery.py`,模拟 Redis、DB、MQ 连接中断后的自动恢复:Worker 启动时 Redis 注册失败重试、消费者断连自动重连、锁引擎中断后消息重入队并在 Redis 恢复后继续处理、DB 瞬断重入队、执行器异常标记 failed/retry、crontab 调度器 Redis 恢复检测等
|
|
817
|
-
- **测试套件稳定性修复** — `test_worker_registry` 隔离全局 `_active_workers` 消除跨用例污染;`test_integration` 动态探测本地可用的 Redis/RabbitMQ 镜像(支持 `DJANGO_SIMPLETASK_REDIS_IMAGE` / `DJANGO_SIMPLETASK_RABBITMQ_IMAGE` 环境变量覆盖),并改为真实 AMQP 握手探测 RabbitMQ 就绪,避免新建容器连接被重置的偶发失败
|
|
818
|
-
- **测试服务脚本** — `start-test-services.sh` 默认使用 6379/5672 端口并动态检测本地镜像,提供 `start` / `stop` / `status` 子命令,便于一键启动测试依赖
|
|
819
941
|
|
|
820
942
|
### 0.2.5
|
|
821
943
|
|
|
@@ -853,14 +975,12 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
853
975
|
|
|
854
976
|
- **拒绝回退机制** — 新增 `RejectException`,执行器可抛出该异常拒绝当前任务,消息自动重新入队等待下次调度,不消耗重试次数
|
|
855
977
|
- **区分两种超时** — 引入 `pickup_timeout_seconds`(等待 Worker 消费超时)与 `timeout_seconds`(执行超时)两种超时配置,`expire_time` 改用 Pickup 超时计算,超时检测更精准
|
|
856
|
-
- **文档更新** — README 补充拒绝回退机制的使用说明和两种超时的详细解释
|
|
857
978
|
|
|
858
979
|
### 0.2.0
|
|
859
980
|
|
|
860
981
|
- **破坏性变更**: 移除 `Task` 模型上的自定义主键,`task_id` 不再是主键字段,改为 `unique=True` 的唯一标识字段。所有 `Task` 子模型将自动获得 Django 默认的自增 `id` 主键。已有数据库需要迁移处理。
|
|
861
|
-
- **统计维度调整**: `TaskExecutionStat` 的统计维度由 `task_model` 改为 `executor_class`,统一覆盖有模型任务和 Cron
|
|
982
|
+
- **统计维度调整**: `TaskExecutionStat` 的统计维度由 `task_model` 改为 `executor_class`,统一覆盖有模型任务和 Cron 定时任务两种场景。
|
|
862
983
|
- **Admin 优化**: CronJob 列表页移除 `executor_class` 列避免表格撑开;TaskExecutionStat 列表页新增 `executor_class` 列、`created_at` 设为只读修复详情页错误。
|
|
863
|
-
- **测试修复**: 修复 `TransactionTestCase` + `on_commit` 在 SQLite 共享内存连接下因 `TestCase` 残留原子块导致回调不执行的兼容性问题;Redis MQ / RabbitMQ E2E 集成测试全部通过。
|
|
864
984
|
|
|
865
985
|
### 0.1.5
|
|
866
986
|
|
|
@@ -876,7 +996,6 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
876
996
|
|
|
877
997
|
- **修复打包** — `pyproject.toml` 添加 `package-data` 配置,打包时包含 locale po/mo 文件,修复 i18n 翻译不生效的问题
|
|
878
998
|
- **补充依赖** — `requirements.txt` 和 `pyproject.toml` 添加缺失的 `django-checkbox-normalize`、`django-tabbed-changeform-admin` 依赖
|
|
879
|
-
- **完善文档** — README 新增完整配置说明章节,涵盖消息队列(MQ)、分布式锁、队列路由、归档、Cron、安全等全部配置项
|
|
880
999
|
|
|
881
1000
|
### 0.1.3
|
|
882
1001
|
|