django-simpletask5 0.2.8__tar.gz → 0.2.9__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.8 → django_simpletask5-0.2.9}/PKG-INFO +109 -47
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/README.md +106 -43
- django_simpletask5-0.2.9/django_simpletask5/__init__.py +13 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/admin.py +150 -24
- django_simpletask5-0.2.9/django_simpletask5/apps.py +25 -0
- django_simpletask5-0.2.9/django_simpletask5/core/__init__.py +1 -0
- django_simpletask5-0.2.9/django_simpletask5/core/cronjob_registry.py +207 -0
- django_simpletask5-0.2.9/django_simpletask5/core/defaults.py +86 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/core/executor_scanner.py +47 -0
- django_simpletask5-0.2.9/django_simpletask5/core/lock.py +43 -0
- django_simpletask5-0.2.9/django_simpletask5/core/logging_utils.py +106 -0
- django_simpletask5-0.2.9/django_simpletask5/core/message_queue.py +269 -0
- django_simpletask5-0.2.9/django_simpletask5/core/publisher.py +372 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/core/reclaim.py +52 -5
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/core/security.py +10 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/core/signals.py +71 -4
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/core/worker_registry.py +80 -7
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/dashboards.py +22 -2
- django_simpletask5-0.2.9/django_simpletask5/executors/__init__.py +1 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/executors/archive.py +15 -0
- django_simpletask5-0.2.9/django_simpletask5/executors/base.py +63 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/executors/bash_script.py +22 -0
- django_simpletask5-0.2.9/django_simpletask5/executors/exceptions.py +14 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/executors/loader.py +10 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/executors/ping_pong.py +5 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/executors/python_script.py +18 -0
- django_simpletask5-0.2.9/django_simpletask5/executors/retry_timeout.py +106 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/executors/simple_request.py +17 -1
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/executors/status_check.py +8 -0
- django_simpletask5-0.2.9/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.po +13 -0
- django_simpletask5-0.2.9/django_simpletask5/management/__init__.py +1 -0
- django_simpletask5-0.2.9/django_simpletask5/management/commands/__init__.py +1 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_archive.py +16 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_crontab.py +69 -7
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_executor.py +130 -14
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/management/commands/django_simpletask_queues.py +22 -1
- django_simpletask5-0.2.9/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +24 -0
- django_simpletask5-0.2.9/django_simpletask5/migrations/0014_cronjob_is_active_modified_by_user.py +22 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/models.py +195 -7
- django_simpletask5-0.2.9/django_simpletask5/services/__init__.py +1 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/services/archive.py +62 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/PKG-INFO +109 -47
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/SOURCES.txt +1 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/requires.txt +1 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/pyproject.toml +3 -4
- django_simpletask5-0.2.8/django_simpletask5/__init__.py +0 -10
- django_simpletask5-0.2.8/django_simpletask5/apps.py +0 -13
- django_simpletask5-0.2.8/django_simpletask5/core/cronjob_registry.py +0 -130
- django_simpletask5-0.2.8/django_simpletask5/core/defaults.py +0 -39
- django_simpletask5-0.2.8/django_simpletask5/core/lock.py +0 -22
- django_simpletask5-0.2.8/django_simpletask5/core/logging_utils.py +0 -17
- django_simpletask5-0.2.8/django_simpletask5/core/message_queue.py +0 -143
- django_simpletask5-0.2.8/django_simpletask5/core/publisher.py +0 -192
- django_simpletask5-0.2.8/django_simpletask5/executors/__init__.py +0 -0
- django_simpletask5-0.2.8/django_simpletask5/executors/base.py +0 -35
- django_simpletask5-0.2.8/django_simpletask5/executors/exceptions.py +0 -13
- django_simpletask5-0.2.8/django_simpletask5/executors/retry_timeout.py +0 -65
- django_simpletask5-0.2.8/django_simpletask5/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- django_simpletask5-0.2.8/django_simpletask5/management/__init__.py +0 -0
- django_simpletask5-0.2.8/django_simpletask5/management/commands/__init__.py +0 -0
- django_simpletask5-0.2.8/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +0 -14
- django_simpletask5-0.2.8/django_simpletask5/migrations/__init__.py +0 -0
- django_simpletask5-0.2.8/django_simpletask5/services/__init__.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/LICENSE +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0001_initial.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0002_taskexecution_done_tasks_count_and_more.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0003_alter_cronjob_options_remove_cronjob_name_and_more.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0004_alter_cronjob_is_modified_by_user.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0005_alter_cronjob_is_modified_by_user_help.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0006_alter_cronjob_options_alter_taskexecution_created_at_and_more.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0007_rename_task_model_to_executor_class_in_stat.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0008_taskexecution_pickup_timeout_seconds.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0009_add_cronjob_app_label.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0010_taskexecution_id_alter_taskexecution_execution_id.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0011_taskexecutionstat_canceled_count.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0012_alter_taskexecution_status.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/migrations/0013_alter_taskexecution_task_id.py +0 -0
- {django_simpletask5-0.2.8/django_simpletask5/core → django_simpletask5-0.2.9/django_simpletask5/migrations}/__init__.py +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/static/django_simpletask5/css/dashboard.css +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5/static/django_simpletask5/js/select2_executor.js +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/dependency_links.txt +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/django_simpletask5.egg-info/top_level.txt +0 -0
- {django_simpletask5-0.2.8 → django_simpletask5-0.2.9}/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.9
|
|
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>
|
|
@@ -15,14 +15,13 @@ Classifier: Framework :: Django :: 5.1
|
|
|
15
15
|
Classifier: Framework :: Django :: 5.2
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.10
|
|
24
22
|
Description-Content-Type: text/markdown
|
|
25
23
|
License-File: LICENSE
|
|
24
|
+
Requires-Dist: Django>=4.2
|
|
26
25
|
Requires-Dist: globallock>=0.2.0
|
|
27
26
|
Requires-Dist: django-safe-fields>=0.2.3
|
|
28
27
|
Requires-Dist: django-app-requires>=0.3.6
|
|
@@ -51,7 +50,7 @@ Dynamic: license-file
|
|
|
51
50
|
- **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
|
|
52
51
|
- **灵活的执行器映射** — 不同事件可绑定不同的执行器类
|
|
53
52
|
- **队列路由** — 不同事件可路由到不同优先级队列
|
|
54
|
-
- **重试与超时** —
|
|
53
|
+
- **重试与超时** — 失败自动重试(指数退避),支持 Pickup 超时检测;运行中孤儿任务由执行锁回收
|
|
55
54
|
- **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
|
|
56
55
|
- **加密字段** — 敏感数据自动加密存储
|
|
57
56
|
- **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
|
|
@@ -61,10 +60,10 @@ Dynamic: license-file
|
|
|
61
60
|
|
|
62
61
|
## 依赖
|
|
63
62
|
|
|
64
|
-
- Python >= 3.
|
|
65
|
-
- Django >=
|
|
66
|
-
- Kombu >= 5.
|
|
67
|
-
- Redis >= 4.0
|
|
63
|
+
- Python >= 3.10
|
|
64
|
+
- Django >= 4.2(兼容至 5.2.x)
|
|
65
|
+
- Kombu >= 5.6.2(消息队列,支持 RabbitMQ/Redis/内存)
|
|
66
|
+
- Redis >= 7.4.0(分布式锁、Worker 注册中心)
|
|
68
67
|
- 详见 `pyproject.toml`
|
|
69
68
|
|
|
70
69
|
## 安装
|
|
@@ -134,6 +133,8 @@ INSTALLED_APPS = [
|
|
|
134
133
|
# ── 消息队列(MQ)────────────────────────────────────────────
|
|
135
134
|
# 生产使用 RabbitMQ 或 Redis;开发调试可用内存队列
|
|
136
135
|
DJANGO_SIMPLETASK_BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672//'
|
|
136
|
+
DJANGO_SIMPLETASK_BROKER_HEARTBEAT = 60 # 发布连接池的 broker 心跳(秒)
|
|
137
|
+
DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES = 100 # AMQP 消息最大重投次数,超过后丢弃
|
|
137
138
|
# 若不设 BROKER_URL,则按以下参数组合:
|
|
138
139
|
# DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE = 'rabbitmq' # 'memory' | 'rabbitmq' | 'redis'
|
|
139
140
|
# DJANGO_SIMPLETASK_RABBITMQ_HOST = '127.0.0.1'
|
|
@@ -172,16 +173,28 @@ DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'myproject:django-simpletask5:queue:high
|
|
|
172
173
|
|
|
173
174
|
# ── 执行与重试 ───────────────────────────────────────────────
|
|
174
175
|
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
175
|
-
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 #
|
|
176
|
+
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 执行超时字段(秒,当前不主动中断)
|
|
176
177
|
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # Pickup 超时(秒)
|
|
177
178
|
|
|
179
|
+
# ── 发布重试 ────────────────────────────────────────────────
|
|
180
|
+
DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY = {
|
|
181
|
+
'max_retries': 2,
|
|
182
|
+
'interval_start': 0.2,
|
|
183
|
+
'interval_step': 0.3,
|
|
184
|
+
'interval_max': 1.0,
|
|
185
|
+
}
|
|
186
|
+
DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY = 1.0 # 应用层第 1 次重试等待(秒)
|
|
187
|
+
DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY = 5.0 # 应用层重试等待上限(秒)
|
|
188
|
+
|
|
178
189
|
# ── Worker / 日志运维参数 ────────────────────────────────────
|
|
179
|
-
DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL = 60 # Worker
|
|
180
|
-
DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = 30 #
|
|
181
|
-
DJANGO_SIMPLETASK_LOG_INTERVAL = 300 #
|
|
190
|
+
DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL = 60 # Worker 心跳/巡检间隔(秒)
|
|
191
|
+
DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = 30 # 每个 worker 线程的优雅停机等待(秒)
|
|
192
|
+
DJANGO_SIMPLETASK_LOG_INTERVAL = 300 # 存活/注册失败等日志节流间隔(秒)
|
|
193
|
+
DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL = 60 # 发布/回收失败日志节流间隔(秒)
|
|
182
194
|
|
|
183
195
|
# ── 归档与统计 ──────────────────────────────────────────────
|
|
184
|
-
|
|
196
|
+
# 归档文件通过 TaskExecutionArchive.file 写入 MEDIA_ROOT/django_simpletask5_archives/%Y/%m/
|
|
197
|
+
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|
185
198
|
DJANGO_SIMPLETASK_ARCHIVE_SALT = 'change-me-archive-salt' # 归档加密盐值
|
|
186
199
|
DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS = 7
|
|
187
200
|
|
|
@@ -214,18 +227,24 @@ django_app_requires_patch_all()
|
|
|
214
227
|
| `DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE` | `'memory'` | `memory` 仅用于开发调试;**生产必须改为 `rabbitmq` 或 `redis`**,否则消息不持久化、跨进程 Worker 无法消费 |
|
|
215
228
|
| `DJANGO_SIMPLETASK_RABBITMQ_HOST/PORT/USER/PASSWORD` | `127.0.0.1 / 5672 / guest / guest` | 类型为 `rabbitmq` 且未设 `BROKER_URL` 时生效。生产务必更换默认 `guest` 凭据 |
|
|
216
229
|
| `DJANGO_SIMPLETASK_REDIS_MQ_HOST/PORT/DB/PASSWORD` | `127.0.0.1 / 6379 / 0 / None` | 类型为 `redis` 时生效。建议与锁/缓存使用**不同 db**,避免 key 冲突与互相阻塞。Redis 需认证时设置 `PASSWORD` |
|
|
230
|
+
| `DJANGO_SIMPLETASK_BROKER_HEARTBEAT` | `60` | Kombu 发布连接池的 broker heartbeat(秒)。网络中间设备会断开空闲连接时建议保持开启 |
|
|
231
|
+
| `DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES` | `100` | AMQP 消息被 reject/requeue 的最大次数,超过后 ack 丢弃,防止毒消息无限循环;memory/redis 传输无 `x-death` 时不生效 |
|
|
217
232
|
| `CACHES['default']` | — | 默认锁引擎 `DjangoRedisGlobalLock` 依赖它,**必须配置为 Redis 缓存** |
|
|
218
233
|
| `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`** |
|
|
219
234
|
| `DJANGO_SIMPLETASK_KEY_PREFIX` | 自动取 `ROOT_URLCONF` 首段 | 生产建议**显式指定**,多项目共用 Redis 时避免 key 冲突 |
|
|
220
235
|
| `DJANGO_SIMPLETASK_DEFAULT_QUEUE` | `{prefix}:...:queue:default` | 默认队列名。多环境(dev/staging/prod)建议加环境后缀隔离 |
|
|
221
236
|
| `DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE` | `{prefix}:...:queue:high_priority` | 高优先级队列,**需单独启动 Worker 消费** |
|
|
222
237
|
| `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES` | `3` | 默认最大重试次数。执行器幂等性好的任务可加大 |
|
|
223
|
-
| `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS` | `600` |
|
|
238
|
+
| `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS` | `600` | 写入 `TaskExecution.timeout_seconds`,并用于计算 running 时的 `expire_time`。当前版本不会因此主动中断 running 任务;严格超时应由执行器自行实现 |
|
|
224
239
|
| `DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS` | `3600` | Pickup 超时(等待 Worker 消费的上限),**通常大于执行超时**,因为排队等待时间不可控 |
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
240
|
+
| `DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY` | `{'max_retries': 2, 'interval_start': 0.2, 'interval_step': 0.3, 'interval_max': 1.0}` | Kombu Producer 内层重试策略。应用层还会做最多 3 次发布尝试 |
|
|
241
|
+
| `DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY` | `1.0` | 应用层发布重试第 1 次等待(秒),之后按指数退避 |
|
|
242
|
+
| `DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY` | `5.0` | 应用层单次发布重试等待上限(秒) |
|
|
243
|
+
| `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL` | `60` | Worker 心跳/巡检间隔,应**小于 Worker 注册 TTL(300 秒)**,否则注册状态被误清理 |
|
|
244
|
+
| `DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT` | `30` | **每个** worker 线程的优雅停机等待时长;总等待时间可能为 worker 数乘以该值 |
|
|
245
|
+
| `DJANGO_SIMPLETASK_LOG_INTERVAL` | `300` | Worker 注册失败、Crontab 错误等日志的节流间隔。日志量大时可调大 |
|
|
246
|
+
| `DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL` | `60` | 发布、重投、孤儿回收失败日志的节流间隔(秒) |
|
|
247
|
+
| `DJANGO_SIMPLETASK_ARCHIVE_PATH` | `django_simpletask5_archives` | **当前版本未读取,仅保留兼容**。归档文件实际写入 `MEDIA_ROOT/django_simpletask5_archives/%Y/%m/` |
|
|
229
248
|
| `DJANGO_SIMPLETASK_ARCHIVE_SALT` | `django-simpletask5-archive` | 归档加密盐值,**生产必须修改**(结合 `SECRET_KEY` 派生 AES 密钥) |
|
|
230
249
|
| `DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS` | `7` | 归档保留天数,按合规要求调整 |
|
|
231
250
|
| `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC` | `True` | 自动把代码注册的 Cron 同步到数据库;只想手工管理时设为 `False` |
|
|
@@ -238,6 +257,8 @@ django_app_requires_patch_all()
|
|
|
238
257
|
|
|
239
258
|
> 注意:`DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS` 设为 `True` 后,还需为用户/组授予 `django_simpletask5 | Cron job | Can use script executors` 权限,非超级管理员无法创建 Python/Shell 脚本执行器类型的定时任务。
|
|
240
259
|
|
|
260
|
+
> 发布可靠性说明:应用层 `_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
|
+
|
|
241
262
|
## 快速开始(从零到一集成)
|
|
242
263
|
|
|
243
264
|
本框架的集成分三步:**定义任务模型 → 编写执行器 → 启动 Worker**。完整可运行的参考实现见仓库中的 `django_simpletask5_example` 应用,包含 `OrderTask` 模型、创建/更新/删除/退款四个执行器及内置 Cron 任务。
|
|
@@ -292,7 +313,7 @@ from django_simpletask5.executors.base import BaseExecutor
|
|
|
292
313
|
from django_simpletask5.models import TaskExecution
|
|
293
314
|
|
|
294
315
|
class OrderCreateExecutor(BaseExecutor):
|
|
295
|
-
timeout_seconds = 300 #
|
|
316
|
+
timeout_seconds = 300 # 记录预期执行上限(当前不主动中断,可选)
|
|
296
317
|
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟(可选)
|
|
297
318
|
|
|
298
319
|
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
@@ -486,7 +507,7 @@ OrderTask.objects.create(..., skip_publish=True) # 方式四:构造参数(
|
|
|
486
507
|
| `get_tasks(execution)` | 返回可迭代对象,默认 `yield None`(单任务) |
|
|
487
508
|
| `get_tasks_count(execution)` | 任务总数(用于进度展示),默认 `1` |
|
|
488
509
|
| `reject(execution, reason='')` | 拒绝任务:状态复位 `pending`、清空 `started_at`、刷新 `expire_time` |
|
|
489
|
-
| `timeout_seconds` / `pickup_timeout_seconds` |
|
|
510
|
+
| `timeout_seconds` / `pickup_timeout_seconds` | 类属性。`pickup_timeout_seconds` 用于自动超时判定;`timeout_seconds` 当前仅记录预期执行上限,不主动中断任务 |
|
|
490
511
|
| `get_parameter_schema()` | 声明执行器参数,用于 Admin 参数配置界面(默认空) |
|
|
491
512
|
|
|
492
513
|
### execute 签名与返回值
|
|
@@ -558,12 +579,14 @@ class MyExecutor(BaseExecutor):
|
|
|
558
579
|
|
|
559
580
|
```python
|
|
560
581
|
class OrderCreateExecutor(BaseExecutor):
|
|
561
|
-
timeout_seconds = 300 #
|
|
582
|
+
timeout_seconds = 300 # 记录预期执行上限(当前不主动中断)
|
|
562
583
|
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
|
|
563
584
|
```
|
|
564
585
|
|
|
565
586
|
未配置时使用全局默认值 `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS`(600 秒)与 `DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS`(3600 秒)。
|
|
566
587
|
|
|
588
|
+
> 注意:当前版本只有 `pickup_timeout_seconds` 会被框架自动判定并触发 `timeout`;`timeout_seconds` 不会中止正在运行的执行器。需要严格执行时限时,请在 `execute()` 内部自行实现超时或使用可取消的子进程。
|
|
589
|
+
|
|
567
590
|
## 模型与生命周期
|
|
568
591
|
|
|
569
592
|
### Task 抽象模型
|
|
@@ -608,21 +631,23 @@ class OrderTask(Task):
|
|
|
608
631
|
|
|
609
632
|
```
|
|
610
633
|
pending ──(Worker 消费)──▶ running ──(执行成功)──▶ success
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
634
|
+
│ │
|
|
635
|
+
│ ├─(异常且可重试)────────▶ retry ──(重新入队并被消费)──▶ running
|
|
636
|
+
│ ├─(异常且重试耗尽)──────▶ failed
|
|
637
|
+
│ └─(执行器异常退出/锁丢失)─▶ retry 或 failed(由 reclaim 回收)
|
|
638
|
+
├─(pickup 超时,从未被领取)──▶ timeout ──(RetryTimeout)──▶ retry
|
|
639
|
+
├─(用户取消)────────────────▶ canceled(终态)
|
|
640
|
+
└─(发布最终失败)────────────▶ failed
|
|
616
641
|
```
|
|
617
642
|
|
|
618
643
|
| 状态 | 含义 |
|
|
619
644
|
|---|---|
|
|
620
|
-
| `pending` | 已发布,等待 Worker
|
|
621
|
-
| `running` |
|
|
645
|
+
| `pending` | 已发布,等待 Worker 消费;拒绝回退也会回到该状态 |
|
|
646
|
+
| `running` | 已获取执行锁,正在执行 |
|
|
622
647
|
| `success` | 执行成功 |
|
|
623
|
-
| `failed` |
|
|
624
|
-
| `retry` |
|
|
625
|
-
| `timeout` | 超过 `expire_time`
|
|
648
|
+
| `failed` | 重试次数耗尽、发布最终失败,或孤儿回收时已无重试次数 |
|
|
649
|
+
| `retry` | 失败/超时后等待重新投递,消息已重新入队 |
|
|
650
|
+
| `timeout` | **pending** 超过 pickup 期限仍未被领取;running 不会因 `expire_time` 自动变成 timeout |
|
|
626
651
|
| `canceled` | 被用户主动取消(终态,Worker 不会执行) |
|
|
627
652
|
|
|
628
653
|
### 取消任务
|
|
@@ -642,29 +667,29 @@ TaskExecution.objects.filter(
|
|
|
642
667
|
### 重试与超时
|
|
643
668
|
|
|
644
669
|
- **重试**:`max_retries`(默认 3)控制最大重试次数,`retry_count` 累计已重试次数;重试消息重新发布到原队列
|
|
645
|
-
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
- **超时检测与恢复**:内置 `StatusCheckExecutor`(内置 Cron)将超时的 `pending`/`running` 记录标记为 `timeout`;`RetryTimeoutExecutor` 将 `timeout` 记录重新入队重试(执行超时消耗重试次数,Pickup 超时不消耗)
|
|
670
|
+
- **Pickup 超时**(`pickup_timeout_seconds`):**唯一由框架自动判定的超时**。`pending` 超过 `expire_time` 后由 `StatusCheckExecutor` 标记为 `timeout`,也用于拒绝回退后的重新调度保护;Pickup 超时不消耗重试次数
|
|
671
|
+
- **执行超时字段**(`timeout_seconds`):写入 `TaskExecution.timeout_seconds`,并在任务进入 `running` 时用于计算 `expire_time`。当前版本**不会**因该时间到达而主动中断 running 任务;如果执行器 hang 住,需在执行器内部实现超时/取消机制
|
|
672
|
+
- **超时检测与恢复**:`StatusCheckExecutor` 每分钟把超过 pickup 期限的 `pending` 记录标记为 `timeout`;`RetryTimeoutExecutor` 每分钟把 `timeout` 记录重新入队,实际运行过的记录消耗一次重试次数。running 孤儿任务由 crontab 基于执行锁过期回收,而不是按 `expire_time` 判超时
|
|
649
673
|
|
|
650
674
|
### 其他模型
|
|
651
675
|
|
|
652
676
|
- `CronJob` — 定时任务注册表,支持代码注册与数据库覆盖(见「Cron 任务」)
|
|
653
|
-
- `TaskExecutionArchive` —
|
|
654
|
-
- `TaskExecutionStat` —
|
|
677
|
+
- `TaskExecutionArchive` — 归档记录,终态执行记录归档为加密 JSONL(见配置「归档」)
|
|
678
|
+
- `TaskExecutionStat` — 按日期、`executor_class`、`trigger_event` 聚合的日统计
|
|
655
679
|
|
|
656
680
|
## Cron 任务
|
|
657
681
|
|
|
658
682
|
### 在代码中定义 Cron 任务
|
|
659
683
|
|
|
660
|
-
在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob`
|
|
684
|
+
在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob` 注册,并在该 app 的 `AppConfig.ready()` 中导入这个模块;框架不会自动发现各 app 下的 `cronjobs.py`:
|
|
661
685
|
|
|
662
686
|
```python
|
|
663
687
|
# myapp/cronjobs.py
|
|
664
|
-
from django_simpletask5.cronjob_registry import register_cronjob
|
|
688
|
+
from django_simpletask5.core.cronjob_registry import register_cronjob
|
|
665
689
|
|
|
666
690
|
register_cronjob(
|
|
667
|
-
|
|
691
|
+
uid='health_check',
|
|
692
|
+
display_name='Health Check',
|
|
668
693
|
cron_expression='*/5 * * * *',
|
|
669
694
|
executor_class='django_simpletask5.executors.simple_request.SimpleRequestExecutor',
|
|
670
695
|
context={
|
|
@@ -676,15 +701,33 @@ register_cronjob(
|
|
|
676
701
|
)
|
|
677
702
|
```
|
|
678
703
|
|
|
704
|
+
```python
|
|
705
|
+
# myapp/apps.py
|
|
706
|
+
from django.apps import AppConfig
|
|
707
|
+
|
|
708
|
+
class MyAppConfig(AppConfig):
|
|
709
|
+
name = 'myapp'
|
|
710
|
+
|
|
711
|
+
def ready(self):
|
|
712
|
+
import myapp.cronjobs # noqa: F401
|
|
713
|
+
```
|
|
714
|
+
|
|
679
715
|
### 从代码同步到数据库
|
|
680
716
|
|
|
681
|
-
注册的 Cron
|
|
717
|
+
注册的 Cron 任务需要同步到数据库才会生效。`django_simpletask_crontab` 运行期间每个 tick 都会自动同步(默认开启,可通过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = False` 关闭自动同步),也可以手动执行:
|
|
682
718
|
|
|
683
719
|
```bash
|
|
684
720
|
python manage.py django_simpletask_sync_cronjobs
|
|
685
721
|
```
|
|
686
722
|
|
|
687
|
-
|
|
723
|
+
手动同步命令使用 `sync_cronjobs(force=True)`,即使关闭了自动同步也会执行。同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。单个 CronJob 创建失败只记录日志,不会中断其它任务同步。
|
|
724
|
+
|
|
725
|
+
启用/停用(`is_active`)的所有权独立跟踪,规则与 `is_modified_by_user` 对称——**手动改过就一直归你,直到显式重置**:
|
|
726
|
+
|
|
727
|
+
- 用户**未**手动启停过的任务,`is_active` 会按代码注册值同步(例如 `simpletask_ping_pong` 注册为 `is_active=False`,默认保持停用)。
|
|
728
|
+
- 用户手动暂停/启用后,会置 `is_active_modified_by_user` 标记,自动同步不再覆盖该状态,但**不影响**其它定义字段(`cron_expression` 等仍照常同步)。
|
|
729
|
+
- 该标记不会自动清除;需要让它重新跟随代码时,使用管理后台的“从代码定义重置”(`reset_from_code`),或在编辑页取消勾选“保留手动启用状态”。
|
|
730
|
+
- 这样手动启停的状态可稳定保持,不会因代码默认值变化而悄悄被改写。
|
|
688
731
|
|
|
689
732
|
> 注意:cron 表达式按 Django 的 `TIME_ZONE` 设置对应的本地时间解释。`USE_TZ=True` 时仍以本地时间计算触发时刻(存储为 UTC),例如 `TIME_ZONE = 'Asia/Shanghai'` 下 `30 14 * * *` 表示每天 14:30 本地时间触发。
|
|
690
733
|
|
|
@@ -692,13 +735,14 @@ python manage.py django_simpletask_sync_cronjobs
|
|
|
692
735
|
|
|
693
736
|
| 执行器 | 说明 |
|
|
694
737
|
|---|---|
|
|
695
|
-
| `PingPongExecutor` | 健康检查,返回 `'pong'
|
|
738
|
+
| `PingPongExecutor` | 健康检查,返回 `'pong'`;内置 Cron 默认停用 |
|
|
696
739
|
| `BashScriptExecutor` | 执行 Shell 脚本 |
|
|
697
740
|
| `PythonScriptExecutor` | 执行 Python 代码 |
|
|
698
741
|
| `SimpleRequestExecutor` | 发起 HTTP 请求 |
|
|
699
|
-
| `StatusCheckExecutor` |
|
|
700
|
-
| `RetryTimeoutExecutor` |
|
|
701
|
-
| `ArchiveExecutor` |
|
|
742
|
+
| `StatusCheckExecutor` | 检测超过 pickup 期限、从未被领取的 `pending` 记录并标记 `timeout`(内置 Cron 每分钟) |
|
|
743
|
+
| `RetryTimeoutExecutor` | 将 `timeout` 记录重新入队(内置 Cron 每分钟;运行过的记录消耗一次重试) |
|
|
744
|
+
| `ArchiveExecutor` | 归档前一天的终态执行并生成统计(内置 Cron 每天 2:00) |
|
|
745
|
+
| `PurgeExpiredExecutor` | 删除超过 `ARCHIVE_RETENTION_DAYS` 的终态执行记录(内置 Cron 每天 3:00;不删除归档文件) |
|
|
702
746
|
|
|
703
747
|
## 开发与测试
|
|
704
748
|
|
|
@@ -732,6 +776,24 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
732
776
|
|
|
733
777
|
## Releases
|
|
734
778
|
|
|
779
|
+
### 0.2.9
|
|
780
|
+
|
|
781
|
+
- **启用状态所有权独立跟踪** — 新增 `CronJob.is_active_modified_by_user` 字段与迁移 `0014`,将“启用/暂停状态”与“定义字段”的所有权彻底分离。用户手动启停(快捷按钮 / 批量操作 / 编辑表单)后置该标记,自动同步(`sync_cronjobs`)不再覆盖 `is_active`;未手动改过的任务仍按代码注册值同步。暂停只锁 `is_active`,`cron_expression` 等定义字段照常同步
|
|
782
|
+
- **显式释放所有权** — 该标记不会自动清除;需要让任务重新跟随代码时,使用管理后台“从代码定义重置”(`reset_from_code`),或在编辑页取消勾选“保留手动启用状态”
|
|
783
|
+
- **保留列表分页/过滤条件** — 修复暂停/运行按钮提交后重定向丢失查询串的问题:按钮表单 `action` 及视图重定向均携带当前 changelist 的 `GET` 参数,操作后刷新仍停留在原分页、过滤器与搜索条件
|
|
784
|
+
- **消息发布可靠性增强** — Kombu 发布复用连接池并支持 broker heartbeat;`publish()` 返回 `queue` / `message_count` / `consumer_count` 队列统计;新增可配置发布重试策略和递增重试延迟;发布异常时自动丢弃损坏连接;新增公开的 `publish_message()` 与兜底的 `safe_publish_message()`,最终发布失败会将 execution 标记为 `failed`
|
|
785
|
+
- **发布日志节流** — 发布、重试投递、孤儿任务回收和 CronJob 触发失败统一接入日志节流,避免 broker/Redis 长时间不可用时刷屏;重投日志同时记录队列积压与消费者数量
|
|
786
|
+
- **Worker 与回收健壮性** — Worker 注册表 Redis 客户端改为公开 `get_redis_client()`;修正 Worker 清理 key 的 glob 为 `worker:{pid}:*`;`reclaim._lock_config()` 与 `get_lock_manager()` 对齐,只有 `None` 才回退默认锁配置;回收扫描前后关闭旧数据库连接,减少长时间运行后的连接失效
|
|
787
|
+
- **日志初始化修复** — `setup_console_logging()` 不再把 `FileHandler` 误判为控制台 handler,避免已有文件日志时跳过控制台输出
|
|
788
|
+
- **CronJob 同步健壮性** — `sync_cronjobs(force=True)` 支持手动同步命令绕过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC`;单条 CronJob 创建失败不再中断其它任务同步;`django_simpletask_sync_cronjobs` 命令改为强制同步
|
|
789
|
+
- **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()`;依赖旧私有名称的扩展或测试需同步调整
|
|
790
|
+
- **可观测性入口公开** — Dashboard 使用的 Worker 统计入口由 `_get_worker_stats` 改为公开的 `get_worker_stats()`;消费者线程心跳改为读取 `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL`,Worker 注册、Redis 降级和日志节流逻辑补充了完整说明
|
|
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`
|
|
796
|
+
|
|
735
797
|
### 0.2.8
|
|
736
798
|
|
|
737
799
|
- **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
|
|
12
12
|
- **灵活的执行器映射** — 不同事件可绑定不同的执行器类
|
|
13
13
|
- **队列路由** — 不同事件可路由到不同优先级队列
|
|
14
|
-
- **重试与超时** —
|
|
14
|
+
- **重试与超时** — 失败自动重试(指数退避),支持 Pickup 超时检测;运行中孤儿任务由执行锁回收
|
|
15
15
|
- **拒绝回退** — 执行器可通过 `RejectException` 拒绝当前任务,消息自动重新入队等待下次调度
|
|
16
16
|
- **加密字段** — 敏感数据自动加密存储
|
|
17
17
|
- **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
|
|
22
22
|
## 依赖
|
|
23
23
|
|
|
24
|
-
- Python >= 3.
|
|
25
|
-
- Django >=
|
|
26
|
-
- Kombu >= 5.
|
|
27
|
-
- Redis >= 4.0
|
|
24
|
+
- Python >= 3.10
|
|
25
|
+
- Django >= 4.2(兼容至 5.2.x)
|
|
26
|
+
- Kombu >= 5.6.2(消息队列,支持 RabbitMQ/Redis/内存)
|
|
27
|
+
- Redis >= 7.4.0(分布式锁、Worker 注册中心)
|
|
28
28
|
- 详见 `pyproject.toml`
|
|
29
29
|
|
|
30
30
|
## 安装
|
|
@@ -94,6 +94,8 @@ INSTALLED_APPS = [
|
|
|
94
94
|
# ── 消息队列(MQ)────────────────────────────────────────────
|
|
95
95
|
# 生产使用 RabbitMQ 或 Redis;开发调试可用内存队列
|
|
96
96
|
DJANGO_SIMPLETASK_BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672//'
|
|
97
|
+
DJANGO_SIMPLETASK_BROKER_HEARTBEAT = 60 # 发布连接池的 broker 心跳(秒)
|
|
98
|
+
DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES = 100 # AMQP 消息最大重投次数,超过后丢弃
|
|
97
99
|
# 若不设 BROKER_URL,则按以下参数组合:
|
|
98
100
|
# DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE = 'rabbitmq' # 'memory' | 'rabbitmq' | 'redis'
|
|
99
101
|
# DJANGO_SIMPLETASK_RABBITMQ_HOST = '127.0.0.1'
|
|
@@ -132,16 +134,28 @@ DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE = 'myproject:django-simpletask5:queue:high
|
|
|
132
134
|
|
|
133
135
|
# ── 执行与重试 ───────────────────────────────────────────────
|
|
134
136
|
DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES = 3
|
|
135
|
-
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 #
|
|
137
|
+
DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS = 600 # 执行超时字段(秒,当前不主动中断)
|
|
136
138
|
DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS = 3600 # Pickup 超时(秒)
|
|
137
139
|
|
|
140
|
+
# ── 发布重试 ────────────────────────────────────────────────
|
|
141
|
+
DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY = {
|
|
142
|
+
'max_retries': 2,
|
|
143
|
+
'interval_start': 0.2,
|
|
144
|
+
'interval_step': 0.3,
|
|
145
|
+
'interval_max': 1.0,
|
|
146
|
+
}
|
|
147
|
+
DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY = 1.0 # 应用层第 1 次重试等待(秒)
|
|
148
|
+
DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY = 5.0 # 应用层重试等待上限(秒)
|
|
149
|
+
|
|
138
150
|
# ── Worker / 日志运维参数 ────────────────────────────────────
|
|
139
|
-
DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL = 60 # Worker
|
|
140
|
-
DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = 30 #
|
|
141
|
-
DJANGO_SIMPLETASK_LOG_INTERVAL = 300 #
|
|
151
|
+
DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL = 60 # Worker 心跳/巡检间隔(秒)
|
|
152
|
+
DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT = 30 # 每个 worker 线程的优雅停机等待(秒)
|
|
153
|
+
DJANGO_SIMPLETASK_LOG_INTERVAL = 300 # 存活/注册失败等日志节流间隔(秒)
|
|
154
|
+
DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL = 60 # 发布/回收失败日志节流间隔(秒)
|
|
142
155
|
|
|
143
156
|
# ── 归档与统计 ──────────────────────────────────────────────
|
|
144
|
-
|
|
157
|
+
# 归档文件通过 TaskExecutionArchive.file 写入 MEDIA_ROOT/django_simpletask5_archives/%Y/%m/
|
|
158
|
+
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|
145
159
|
DJANGO_SIMPLETASK_ARCHIVE_SALT = 'change-me-archive-salt' # 归档加密盐值
|
|
146
160
|
DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS = 7
|
|
147
161
|
|
|
@@ -174,18 +188,24 @@ django_app_requires_patch_all()
|
|
|
174
188
|
| `DJANGO_SIMPLETASK_MESSAGE_QUEUE_TYPE` | `'memory'` | `memory` 仅用于开发调试;**生产必须改为 `rabbitmq` 或 `redis`**,否则消息不持久化、跨进程 Worker 无法消费 |
|
|
175
189
|
| `DJANGO_SIMPLETASK_RABBITMQ_HOST/PORT/USER/PASSWORD` | `127.0.0.1 / 5672 / guest / guest` | 类型为 `rabbitmq` 且未设 `BROKER_URL` 时生效。生产务必更换默认 `guest` 凭据 |
|
|
176
190
|
| `DJANGO_SIMPLETASK_REDIS_MQ_HOST/PORT/DB/PASSWORD` | `127.0.0.1 / 6379 / 0 / None` | 类型为 `redis` 时生效。建议与锁/缓存使用**不同 db**,避免 key 冲突与互相阻塞。Redis 需认证时设置 `PASSWORD` |
|
|
191
|
+
| `DJANGO_SIMPLETASK_BROKER_HEARTBEAT` | `60` | Kombu 发布连接池的 broker heartbeat(秒)。网络中间设备会断开空闲连接时建议保持开启 |
|
|
192
|
+
| `DJANGO_SIMPLETASK_MAX_REQUEUE_DELIVERIES` | `100` | AMQP 消息被 reject/requeue 的最大次数,超过后 ack 丢弃,防止毒消息无限循环;memory/redis 传输无 `x-death` 时不生效 |
|
|
177
193
|
| `CACHES['default']` | — | 默认锁引擎 `DjangoRedisGlobalLock` 依赖它,**必须配置为 Redis 缓存** |
|
|
178
194
|
| `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`** |
|
|
179
195
|
| `DJANGO_SIMPLETASK_KEY_PREFIX` | 自动取 `ROOT_URLCONF` 首段 | 生产建议**显式指定**,多项目共用 Redis 时避免 key 冲突 |
|
|
180
196
|
| `DJANGO_SIMPLETASK_DEFAULT_QUEUE` | `{prefix}:...:queue:default` | 默认队列名。多环境(dev/staging/prod)建议加环境后缀隔离 |
|
|
181
197
|
| `DJANGO_SIMPLETASK_HIGH_PRIORITY_QUEUE` | `{prefix}:...:queue:high_priority` | 高优先级队列,**需单独启动 Worker 消费** |
|
|
182
198
|
| `DJANGO_SIMPLETASK_DEFAULT_MAX_RETRIES` | `3` | 默认最大重试次数。执行器幂等性好的任务可加大 |
|
|
183
|
-
| `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS` | `600` |
|
|
199
|
+
| `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS` | `600` | 写入 `TaskExecution.timeout_seconds`,并用于计算 running 时的 `expire_time`。当前版本不会因此主动中断 running 任务;严格超时应由执行器自行实现 |
|
|
184
200
|
| `DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS` | `3600` | Pickup 超时(等待 Worker 消费的上限),**通常大于执行超时**,因为排队等待时间不可控 |
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
201
|
+
| `DJANGO_SIMPLETASK_PUBLISH_RETRY_POLICY` | `{'max_retries': 2, 'interval_start': 0.2, 'interval_step': 0.3, 'interval_max': 1.0}` | Kombu Producer 内层重试策略。应用层还会做最多 3 次发布尝试 |
|
|
202
|
+
| `DJANGO_SIMPLETASK_PUBLISH_RETRY_BASE_DELAY` | `1.0` | 应用层发布重试第 1 次等待(秒),之后按指数退避 |
|
|
203
|
+
| `DJANGO_SIMPLETASK_PUBLISH_RETRY_MAX_DELAY` | `5.0` | 应用层单次发布重试等待上限(秒) |
|
|
204
|
+
| `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL` | `60` | Worker 心跳/巡检间隔,应**小于 Worker 注册 TTL(300 秒)**,否则注册状态被误清理 |
|
|
205
|
+
| `DJANGO_SIMPLETASK_SHUTDOWN_TIMEOUT` | `30` | **每个** worker 线程的优雅停机等待时长;总等待时间可能为 worker 数乘以该值 |
|
|
206
|
+
| `DJANGO_SIMPLETASK_LOG_INTERVAL` | `300` | Worker 注册失败、Crontab 错误等日志的节流间隔。日志量大时可调大 |
|
|
207
|
+
| `DJANGO_SIMPLETASK_PUBLISH_LOG_INTERVAL` | `60` | 发布、重投、孤儿回收失败日志的节流间隔(秒) |
|
|
208
|
+
| `DJANGO_SIMPLETASK_ARCHIVE_PATH` | `django_simpletask5_archives` | **当前版本未读取,仅保留兼容**。归档文件实际写入 `MEDIA_ROOT/django_simpletask5_archives/%Y/%m/` |
|
|
189
209
|
| `DJANGO_SIMPLETASK_ARCHIVE_SALT` | `django-simpletask5-archive` | 归档加密盐值,**生产必须修改**(结合 `SECRET_KEY` 派生 AES 密钥) |
|
|
190
210
|
| `DJANGO_SIMPLETASK_ARCHIVE_RETENTION_DAYS` | `7` | 归档保留天数,按合规要求调整 |
|
|
191
211
|
| `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC` | `True` | 自动把代码注册的 Cron 同步到数据库;只想手工管理时设为 `False` |
|
|
@@ -198,6 +218,8 @@ django_app_requires_patch_all()
|
|
|
198
218
|
|
|
199
219
|
> 注意:`DJANGO_SIMPLETASK_ENABLE_SCRIPT_EXECUTORS` 设为 `True` 后,还需为用户/组授予 `django_simpletask5 | Cron job | Can use script executors` 权限,非超级管理员无法创建 Python/Shell 脚本执行器类型的定时任务。
|
|
200
220
|
|
|
221
|
+
> 发布可靠性说明:应用层 `_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()` 保留异常上抛语义,供需要自行处理错误的调用方使用。
|
|
222
|
+
|
|
201
223
|
## 快速开始(从零到一集成)
|
|
202
224
|
|
|
203
225
|
本框架的集成分三步:**定义任务模型 → 编写执行器 → 启动 Worker**。完整可运行的参考实现见仓库中的 `django_simpletask5_example` 应用,包含 `OrderTask` 模型、创建/更新/删除/退款四个执行器及内置 Cron 任务。
|
|
@@ -252,7 +274,7 @@ from django_simpletask5.executors.base import BaseExecutor
|
|
|
252
274
|
from django_simpletask5.models import TaskExecution
|
|
253
275
|
|
|
254
276
|
class OrderCreateExecutor(BaseExecutor):
|
|
255
|
-
timeout_seconds = 300 #
|
|
277
|
+
timeout_seconds = 300 # 记录预期执行上限(当前不主动中断,可选)
|
|
256
278
|
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟(可选)
|
|
257
279
|
|
|
258
280
|
def execute(self, execution: TaskExecution, task=None) -> str | None:
|
|
@@ -446,7 +468,7 @@ OrderTask.objects.create(..., skip_publish=True) # 方式四:构造参数(
|
|
|
446
468
|
| `get_tasks(execution)` | 返回可迭代对象,默认 `yield None`(单任务) |
|
|
447
469
|
| `get_tasks_count(execution)` | 任务总数(用于进度展示),默认 `1` |
|
|
448
470
|
| `reject(execution, reason='')` | 拒绝任务:状态复位 `pending`、清空 `started_at`、刷新 `expire_time` |
|
|
449
|
-
| `timeout_seconds` / `pickup_timeout_seconds` |
|
|
471
|
+
| `timeout_seconds` / `pickup_timeout_seconds` | 类属性。`pickup_timeout_seconds` 用于自动超时判定;`timeout_seconds` 当前仅记录预期执行上限,不主动中断任务 |
|
|
450
472
|
| `get_parameter_schema()` | 声明执行器参数,用于 Admin 参数配置界面(默认空) |
|
|
451
473
|
|
|
452
474
|
### execute 签名与返回值
|
|
@@ -518,12 +540,14 @@ class MyExecutor(BaseExecutor):
|
|
|
518
540
|
|
|
519
541
|
```python
|
|
520
542
|
class OrderCreateExecutor(BaseExecutor):
|
|
521
|
-
timeout_seconds = 300 #
|
|
543
|
+
timeout_seconds = 300 # 记录预期执行上限(当前不主动中断)
|
|
522
544
|
pickup_timeout_seconds = 1800 # Pickup 超时 30 分钟
|
|
523
545
|
```
|
|
524
546
|
|
|
525
547
|
未配置时使用全局默认值 `DJANGO_SIMPLETASK_DEFAULT_TIMEOUT_SECONDS`(600 秒)与 `DJANGO_SIMPLETASK_DEFAULT_PICKUP_TIMEOUT_SECONDS`(3600 秒)。
|
|
526
548
|
|
|
549
|
+
> 注意:当前版本只有 `pickup_timeout_seconds` 会被框架自动判定并触发 `timeout`;`timeout_seconds` 不会中止正在运行的执行器。需要严格执行时限时,请在 `execute()` 内部自行实现超时或使用可取消的子进程。
|
|
550
|
+
|
|
527
551
|
## 模型与生命周期
|
|
528
552
|
|
|
529
553
|
### Task 抽象模型
|
|
@@ -568,21 +592,23 @@ class OrderTask(Task):
|
|
|
568
592
|
|
|
569
593
|
```
|
|
570
594
|
pending ──(Worker 消费)──▶ running ──(执行成功)──▶ success
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
595
|
+
│ │
|
|
596
|
+
│ ├─(异常且可重试)────────▶ retry ──(重新入队并被消费)──▶ running
|
|
597
|
+
│ ├─(异常且重试耗尽)──────▶ failed
|
|
598
|
+
│ └─(执行器异常退出/锁丢失)─▶ retry 或 failed(由 reclaim 回收)
|
|
599
|
+
├─(pickup 超时,从未被领取)──▶ timeout ──(RetryTimeout)──▶ retry
|
|
600
|
+
├─(用户取消)────────────────▶ canceled(终态)
|
|
601
|
+
└─(发布最终失败)────────────▶ failed
|
|
576
602
|
```
|
|
577
603
|
|
|
578
604
|
| 状态 | 含义 |
|
|
579
605
|
|---|---|
|
|
580
|
-
| `pending` | 已发布,等待 Worker
|
|
581
|
-
| `running` |
|
|
606
|
+
| `pending` | 已发布,等待 Worker 消费;拒绝回退也会回到该状态 |
|
|
607
|
+
| `running` | 已获取执行锁,正在执行 |
|
|
582
608
|
| `success` | 执行成功 |
|
|
583
|
-
| `failed` |
|
|
584
|
-
| `retry` |
|
|
585
|
-
| `timeout` | 超过 `expire_time`
|
|
609
|
+
| `failed` | 重试次数耗尽、发布最终失败,或孤儿回收时已无重试次数 |
|
|
610
|
+
| `retry` | 失败/超时后等待重新投递,消息已重新入队 |
|
|
611
|
+
| `timeout` | **pending** 超过 pickup 期限仍未被领取;running 不会因 `expire_time` 自动变成 timeout |
|
|
586
612
|
| `canceled` | 被用户主动取消(终态,Worker 不会执行) |
|
|
587
613
|
|
|
588
614
|
### 取消任务
|
|
@@ -602,29 +628,29 @@ TaskExecution.objects.filter(
|
|
|
602
628
|
### 重试与超时
|
|
603
629
|
|
|
604
630
|
- **重试**:`max_retries`(默认 3)控制最大重试次数,`retry_count` 累计已重试次数;重试消息重新发布到原队列
|
|
605
|
-
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
- **超时检测与恢复**:内置 `StatusCheckExecutor`(内置 Cron)将超时的 `pending`/`running` 记录标记为 `timeout`;`RetryTimeoutExecutor` 将 `timeout` 记录重新入队重试(执行超时消耗重试次数,Pickup 超时不消耗)
|
|
631
|
+
- **Pickup 超时**(`pickup_timeout_seconds`):**唯一由框架自动判定的超时**。`pending` 超过 `expire_time` 后由 `StatusCheckExecutor` 标记为 `timeout`,也用于拒绝回退后的重新调度保护;Pickup 超时不消耗重试次数
|
|
632
|
+
- **执行超时字段**(`timeout_seconds`):写入 `TaskExecution.timeout_seconds`,并在任务进入 `running` 时用于计算 `expire_time`。当前版本**不会**因该时间到达而主动中断 running 任务;如果执行器 hang 住,需在执行器内部实现超时/取消机制
|
|
633
|
+
- **超时检测与恢复**:`StatusCheckExecutor` 每分钟把超过 pickup 期限的 `pending` 记录标记为 `timeout`;`RetryTimeoutExecutor` 每分钟把 `timeout` 记录重新入队,实际运行过的记录消耗一次重试次数。running 孤儿任务由 crontab 基于执行锁过期回收,而不是按 `expire_time` 判超时
|
|
609
634
|
|
|
610
635
|
### 其他模型
|
|
611
636
|
|
|
612
637
|
- `CronJob` — 定时任务注册表,支持代码注册与数据库覆盖(见「Cron 任务」)
|
|
613
|
-
- `TaskExecutionArchive` —
|
|
614
|
-
- `TaskExecutionStat` —
|
|
638
|
+
- `TaskExecutionArchive` — 归档记录,终态执行记录归档为加密 JSONL(见配置「归档」)
|
|
639
|
+
- `TaskExecutionStat` — 按日期、`executor_class`、`trigger_event` 聚合的日统计
|
|
615
640
|
|
|
616
641
|
## Cron 任务
|
|
617
642
|
|
|
618
643
|
### 在代码中定义 Cron 任务
|
|
619
644
|
|
|
620
|
-
在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob`
|
|
645
|
+
在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob` 注册,并在该 app 的 `AppConfig.ready()` 中导入这个模块;框架不会自动发现各 app 下的 `cronjobs.py`:
|
|
621
646
|
|
|
622
647
|
```python
|
|
623
648
|
# myapp/cronjobs.py
|
|
624
|
-
from django_simpletask5.cronjob_registry import register_cronjob
|
|
649
|
+
from django_simpletask5.core.cronjob_registry import register_cronjob
|
|
625
650
|
|
|
626
651
|
register_cronjob(
|
|
627
|
-
|
|
652
|
+
uid='health_check',
|
|
653
|
+
display_name='Health Check',
|
|
628
654
|
cron_expression='*/5 * * * *',
|
|
629
655
|
executor_class='django_simpletask5.executors.simple_request.SimpleRequestExecutor',
|
|
630
656
|
context={
|
|
@@ -636,15 +662,33 @@ register_cronjob(
|
|
|
636
662
|
)
|
|
637
663
|
```
|
|
638
664
|
|
|
665
|
+
```python
|
|
666
|
+
# myapp/apps.py
|
|
667
|
+
from django.apps import AppConfig
|
|
668
|
+
|
|
669
|
+
class MyAppConfig(AppConfig):
|
|
670
|
+
name = 'myapp'
|
|
671
|
+
|
|
672
|
+
def ready(self):
|
|
673
|
+
import myapp.cronjobs # noqa: F401
|
|
674
|
+
```
|
|
675
|
+
|
|
639
676
|
### 从代码同步到数据库
|
|
640
677
|
|
|
641
|
-
注册的 Cron
|
|
678
|
+
注册的 Cron 任务需要同步到数据库才会生效。`django_simpletask_crontab` 运行期间每个 tick 都会自动同步(默认开启,可通过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = False` 关闭自动同步),也可以手动执行:
|
|
642
679
|
|
|
643
680
|
```bash
|
|
644
681
|
python manage.py django_simpletask_sync_cronjobs
|
|
645
682
|
```
|
|
646
683
|
|
|
647
|
-
|
|
684
|
+
手动同步命令使用 `sync_cronjobs(force=True)`,即使关闭了自动同步也会执行。同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。单个 CronJob 创建失败只记录日志,不会中断其它任务同步。
|
|
685
|
+
|
|
686
|
+
启用/停用(`is_active`)的所有权独立跟踪,规则与 `is_modified_by_user` 对称——**手动改过就一直归你,直到显式重置**:
|
|
687
|
+
|
|
688
|
+
- 用户**未**手动启停过的任务,`is_active` 会按代码注册值同步(例如 `simpletask_ping_pong` 注册为 `is_active=False`,默认保持停用)。
|
|
689
|
+
- 用户手动暂停/启用后,会置 `is_active_modified_by_user` 标记,自动同步不再覆盖该状态,但**不影响**其它定义字段(`cron_expression` 等仍照常同步)。
|
|
690
|
+
- 该标记不会自动清除;需要让它重新跟随代码时,使用管理后台的“从代码定义重置”(`reset_from_code`),或在编辑页取消勾选“保留手动启用状态”。
|
|
691
|
+
- 这样手动启停的状态可稳定保持,不会因代码默认值变化而悄悄被改写。
|
|
648
692
|
|
|
649
693
|
> 注意:cron 表达式按 Django 的 `TIME_ZONE` 设置对应的本地时间解释。`USE_TZ=True` 时仍以本地时间计算触发时刻(存储为 UTC),例如 `TIME_ZONE = 'Asia/Shanghai'` 下 `30 14 * * *` 表示每天 14:30 本地时间触发。
|
|
650
694
|
|
|
@@ -652,13 +696,14 @@ python manage.py django_simpletask_sync_cronjobs
|
|
|
652
696
|
|
|
653
697
|
| 执行器 | 说明 |
|
|
654
698
|
|---|---|
|
|
655
|
-
| `PingPongExecutor` | 健康检查,返回 `'pong'
|
|
699
|
+
| `PingPongExecutor` | 健康检查,返回 `'pong'`;内置 Cron 默认停用 |
|
|
656
700
|
| `BashScriptExecutor` | 执行 Shell 脚本 |
|
|
657
701
|
| `PythonScriptExecutor` | 执行 Python 代码 |
|
|
658
702
|
| `SimpleRequestExecutor` | 发起 HTTP 请求 |
|
|
659
|
-
| `StatusCheckExecutor` |
|
|
660
|
-
| `RetryTimeoutExecutor` |
|
|
661
|
-
| `ArchiveExecutor` |
|
|
703
|
+
| `StatusCheckExecutor` | 检测超过 pickup 期限、从未被领取的 `pending` 记录并标记 `timeout`(内置 Cron 每分钟) |
|
|
704
|
+
| `RetryTimeoutExecutor` | 将 `timeout` 记录重新入队(内置 Cron 每分钟;运行过的记录消耗一次重试) |
|
|
705
|
+
| `ArchiveExecutor` | 归档前一天的终态执行并生成统计(内置 Cron 每天 2:00) |
|
|
706
|
+
| `PurgeExpiredExecutor` | 删除超过 `ARCHIVE_RETENTION_DAYS` 的终态执行记录(内置 Cron 每天 3:00;不删除归档文件) |
|
|
662
707
|
|
|
663
708
|
## 开发与测试
|
|
664
709
|
|
|
@@ -692,6 +737,24 @@ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保
|
|
|
692
737
|
|
|
693
738
|
## Releases
|
|
694
739
|
|
|
740
|
+
### 0.2.9
|
|
741
|
+
|
|
742
|
+
- **启用状态所有权独立跟踪** — 新增 `CronJob.is_active_modified_by_user` 字段与迁移 `0014`,将“启用/暂停状态”与“定义字段”的所有权彻底分离。用户手动启停(快捷按钮 / 批量操作 / 编辑表单)后置该标记,自动同步(`sync_cronjobs`)不再覆盖 `is_active`;未手动改过的任务仍按代码注册值同步。暂停只锁 `is_active`,`cron_expression` 等定义字段照常同步
|
|
743
|
+
- **显式释放所有权** — 该标记不会自动清除;需要让任务重新跟随代码时,使用管理后台“从代码定义重置”(`reset_from_code`),或在编辑页取消勾选“保留手动启用状态”
|
|
744
|
+
- **保留列表分页/过滤条件** — 修复暂停/运行按钮提交后重定向丢失查询串的问题:按钮表单 `action` 及视图重定向均携带当前 changelist 的 `GET` 参数,操作后刷新仍停留在原分页、过滤器与搜索条件
|
|
745
|
+
- **消息发布可靠性增强** — Kombu 发布复用连接池并支持 broker heartbeat;`publish()` 返回 `queue` / `message_count` / `consumer_count` 队列统计;新增可配置发布重试策略和递增重试延迟;发布异常时自动丢弃损坏连接;新增公开的 `publish_message()` 与兜底的 `safe_publish_message()`,最终发布失败会将 execution 标记为 `failed`
|
|
746
|
+
- **发布日志节流** — 发布、重试投递、孤儿任务回收和 CronJob 触发失败统一接入日志节流,避免 broker/Redis 长时间不可用时刷屏;重投日志同时记录队列积压与消费者数量
|
|
747
|
+
- **Worker 与回收健壮性** — Worker 注册表 Redis 客户端改为公开 `get_redis_client()`;修正 Worker 清理 key 的 glob 为 `worker:{pid}:*`;`reclaim._lock_config()` 与 `get_lock_manager()` 对齐,只有 `None` 才回退默认锁配置;回收扫描前后关闭旧数据库连接,减少长时间运行后的连接失效
|
|
748
|
+
- **日志初始化修复** — `setup_console_logging()` 不再把 `FileHandler` 误判为控制台 handler,避免已有文件日志时跳过控制台输出
|
|
749
|
+
- **CronJob 同步健壮性** — `sync_cronjobs(force=True)` 支持手动同步命令绕过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC`;单条 CronJob 创建失败不再中断其它任务同步;`django_simpletask_sync_cronjobs` 命令改为强制同步
|
|
750
|
+
- **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()`;依赖旧私有名称的扩展或测试需同步调整
|
|
751
|
+
- **可观测性入口公开** — Dashboard 使用的 Worker 统计入口由 `_get_worker_stats` 改为公开的 `get_worker_stats()`;消费者线程心跳改为读取 `DJANGO_SIMPLETASK_WORKER_HEARTBEAT_INTERVAL`,Worker 注册、Redis 降级和日志节流逻辑补充了完整说明
|
|
752
|
+
- **注释与文档校准** — 为整个 `django_simpletask5` 生产包补充中文模块/类/函数/分支注释,并修正归档路径、归档清理、`running/timeout` 状态流转、Admin 只读性、脚本白名单、连接池 heartbeat、优雅退出等待时间等与实际行为不一致的说明
|
|
753
|
+
- **迁移与翻译** — 新增迁移 `django_simpletask5.0014_cronjob_is_active_modified_by_user`,同步更新简体中文翻译与迁移中的字段 `help_text`
|
|
754
|
+
- **测试** — 新增 `test_logging_utils.py`;扩充消息发布、消息队列、CronJob 同步、Admin 启停、Reclaim、Worker 恢复、Redis 锁与集成测试,覆盖最终发布失败、队列积压统计、`force` 同步、启停所有权、查询串保留、空锁配置和 Worker 清理 glob 等场景
|
|
755
|
+
- **环境要求** — 代码使用 `X | None` 联合类型,最低 Python 版本明确为 3.10;同时将 Django 最低版本明确为 4.2,并补充 `Django>=4.2` 依赖声明
|
|
756
|
+
- **版本** — `pyproject.toml` 版本更新为 `0.2.9`
|
|
757
|
+
|
|
695
758
|
### 0.2.8
|
|
696
759
|
|
|
697
760
|
- **移除 `Task` 冗余的 `task_id` 字段** — `TaskExecution.task_id` 由 UUID 改为 `CharField(max_length=255)`,直接用于存放业务对象主键字符串(`str(instance.pk)`),不再维护一份与业务主键重复的 UUID。消息上下文中的 `task_id` 字段名不变,值语义从 UUID 变为业务主键字符串(如 `"42"`)
|