django-simpletask5 0.1.0__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.
Files changed (43) hide show
  1. django_simpletask5-0.1.0/LICENSE +21 -0
  2. django_simpletask5-0.1.0/PKG-INFO +251 -0
  3. django_simpletask5-0.1.0/README.md +214 -0
  4. django_simpletask5-0.1.0/django_simpletask5/__init__.py +7 -0
  5. django_simpletask5-0.1.0/django_simpletask5/admin.py +205 -0
  6. django_simpletask5-0.1.0/django_simpletask5/apps.py +8 -0
  7. django_simpletask5-0.1.0/django_simpletask5/core/__init__.py +0 -0
  8. django_simpletask5-0.1.0/django_simpletask5/core/cronjob_registry.py +96 -0
  9. django_simpletask5-0.1.0/django_simpletask5/core/defaults.py +24 -0
  10. django_simpletask5-0.1.0/django_simpletask5/core/executor_scanner.py +82 -0
  11. django_simpletask5-0.1.0/django_simpletask5/core/lock.py +24 -0
  12. django_simpletask5-0.1.0/django_simpletask5/core/message_queue.py +102 -0
  13. django_simpletask5-0.1.0/django_simpletask5/core/publisher.py +93 -0
  14. django_simpletask5-0.1.0/django_simpletask5/core/signals.py +120 -0
  15. django_simpletask5-0.1.0/django_simpletask5/core/worker_registry.py +134 -0
  16. django_simpletask5-0.1.0/django_simpletask5/dashboards.py +274 -0
  17. django_simpletask5-0.1.0/django_simpletask5/executors/__init__.py +0 -0
  18. django_simpletask5-0.1.0/django_simpletask5/executors/archive.py +24 -0
  19. django_simpletask5-0.1.0/django_simpletask5/executors/base.py +10 -0
  20. django_simpletask5-0.1.0/django_simpletask5/executors/bash_script.py +61 -0
  21. django_simpletask5-0.1.0/django_simpletask5/executors/loader.py +17 -0
  22. django_simpletask5-0.1.0/django_simpletask5/executors/ping_pong.py +12 -0
  23. django_simpletask5-0.1.0/django_simpletask5/executors/python_script.py +42 -0
  24. django_simpletask5-0.1.0/django_simpletask5/executors/retry_timeout.py +32 -0
  25. django_simpletask5-0.1.0/django_simpletask5/executors/simple_request.py +47 -0
  26. django_simpletask5-0.1.0/django_simpletask5/executors/status_check.py +40 -0
  27. django_simpletask5-0.1.0/django_simpletask5/management/__init__.py +0 -0
  28. django_simpletask5-0.1.0/django_simpletask5/management/commands/__init__.py +0 -0
  29. django_simpletask5-0.1.0/django_simpletask5/management/commands/django_simpletask_crontab.py +169 -0
  30. django_simpletask5-0.1.0/django_simpletask5/management/commands/django_simpletask_executor.py +286 -0
  31. django_simpletask5-0.1.0/django_simpletask5/management/commands/django_simpletask_sync_cronjobs.py +14 -0
  32. django_simpletask5-0.1.0/django_simpletask5/migrations/0001_initial.py +283 -0
  33. django_simpletask5-0.1.0/django_simpletask5/migrations/__init__.py +0 -0
  34. django_simpletask5-0.1.0/django_simpletask5/models.py +274 -0
  35. django_simpletask5-0.1.0/django_simpletask5/services/__init__.py +0 -0
  36. django_simpletask5-0.1.0/django_simpletask5/services/archive.py +198 -0
  37. django_simpletask5-0.1.0/django_simpletask5.egg-info/PKG-INFO +251 -0
  38. django_simpletask5-0.1.0/django_simpletask5.egg-info/SOURCES.txt +41 -0
  39. django_simpletask5-0.1.0/django_simpletask5.egg-info/dependency_links.txt +1 -0
  40. django_simpletask5-0.1.0/django_simpletask5.egg-info/requires.txt +10 -0
  41. django_simpletask5-0.1.0/django_simpletask5.egg-info/top_level.txt +2 -0
  42. django_simpletask5-0.1.0/pyproject.toml +71 -0
  43. django_simpletask5-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rRR0VrFP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,251 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-simpletask5
3
+ Version: 0.1.0
4
+ Summary: Lightweight async task execution framework for Django
5
+ Author-email: rRR0VrFP <rrr0vrfp@qq.com>
6
+ Maintainer-email: rRR0VrFP <rrr0vrfp@qq.com>
7
+ License: MIT
8
+ Project-URL: homepage, https://gitee.com/rRR0VrFP/django-simpletask5
9
+ Keywords: django,async,task,queue,cron,background
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: Django
12
+ Classifier: Framework :: Django :: 4.2
13
+ Classifier: Framework :: Django :: 5.0
14
+ Classifier: Framework :: Django :: 5.1
15
+ Classifier: Framework :: Django :: 5.2
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: globallock>=0.1.4
27
+ Requires-Dist: django-safe-fields>=0.2.3
28
+ Requires-Dist: django-app-requires>=0.3.6
29
+ Requires-Dist: django-admin-dashboards>=0.1.1
30
+ Requires-Dist: cryptography>=48.0.0
31
+ Requires-Dist: croniter>=6.2.2
32
+ Requires-Dist: requests>=2.34.2
33
+ Requires-Dist: kombu>=5.6.2
34
+ Requires-Dist: redis>=7.4.0
35
+ Requires-Dist: pyyaml>=6.0.3
36
+ Dynamic: license-file
37
+
38
+ # django-simpletask5
39
+
40
+ > 本项目由 opencode + deepseek-v4-flash 生成
41
+
42
+ 一个轻量级的 Django 异步任务执行框架,提供声明式的任务模型、信号驱动的自动发布、Worker 进程异步执行,以及内置的 Cron 定时调度。
43
+
44
+ ## 特性
45
+
46
+ - **声明式任务模型** — 继承 `Task` 模型即可定义任务,自动处理创建/更新/删除事件
47
+ - **信号驱动** — Django 信号自动拦截模型变更,发布 `TaskExecution` 到消息队列
48
+ - **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
49
+ - **灵活的执行器映射** — 不同事件可绑定不同的执行器类
50
+ - **队列路由** — 不同事件可路由到不同优先级队列
51
+ - **重试与超时** — 失败自动重试(指数退避),支持超时检测
52
+ - **加密字段** — 敏感数据自动加密存储
53
+ - **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
54
+ - **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
55
+ - **Worker 注册中心** — 基于 Redis 的 Worker 心跳与状态追踪
56
+ - **Django Admin 集成** — 完整的后台管理界面
57
+
58
+ ## 依赖
59
+
60
+ - Python >= 3.8
61
+ - Django >= 3.2(兼容至 5.2.x)
62
+ - Kombu >= 5.3.0(消息队列,支持 RabbitMQ/Redis/内存)
63
+ - Redis >= 4.0.0(分布式锁、Worker 注册中心)
64
+ - 详见 `pyproject.toml`
65
+
66
+ ## 安装
67
+
68
+ ```bash
69
+ pip install django-simpletask5
70
+ ```
71
+
72
+ `django-simpletask5` 使用 [django-app-requires](https://pypi.org/project/django-app-requires/) 自动管理依赖的 app(如 `django_safe_fields`),无需手动添加到 `INSTALLED_APPS`。只需在 `settings.py` 中调用一次 patch:
73
+
74
+ ```python
75
+ from django_app_requires import patch_all as django_app_requires_patch_all
76
+ django_app_requires_patch_all()
77
+ ```
78
+
79
+ 然后在 `INSTALLED_APPS` 中添加:
80
+
81
+ ```python
82
+ INSTALLED_APPS = [
83
+ ...
84
+ 'django_simpletask5',
85
+ ]
86
+ ```
87
+
88
+ 配置分布式锁:
89
+
90
+ ```python
91
+ DJANGO_SIMPLETASK_LOCK_CONFIG = {
92
+ 'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
93
+ 'global_lock_engine_options': {
94
+ 'host': '127.0.0.1',
95
+ 'port': 6379,
96
+ 'db': 0,
97
+ },
98
+ }
99
+ ```
100
+
101
+ 运行迁移:
102
+
103
+ ```bash
104
+ python manage.py migrate django_simpletask5
105
+ ```
106
+
107
+ ## 快速开始
108
+
109
+ ### 1. 定义任务模型
110
+
111
+ ```python
112
+ from django.db import models
113
+ from django_simpletask5.models import Task
114
+
115
+ class OrderTask(Task):
116
+ order_id = models.CharField(max_length=64, unique=True)
117
+ customer_name = models.CharField(max_length=128)
118
+ amount = models.DecimalField(max_digits=10, decimal_places=2)
119
+ status = models.CharField(max_length=32, default='pending')
120
+
121
+ executor_class = {
122
+ 'create': 'myapp.executors.OrderCreateExecutor',
123
+ 'update': 'myapp.executors.OrderUpdateExecutor',
124
+ 'delete': 'myapp.executors.OrderDeleteExecutor',
125
+ }
126
+
127
+ simpletask_queue = {
128
+ 'create': 'django_simpletask5.queue.high_priority',
129
+ 'update': 'django_simpletask5.queue.default',
130
+ 'delete': 'django_simpletask5.queue.default',
131
+ }
132
+
133
+ trigger_update_fields = ['customer_name', 'amount', 'status']
134
+ ```
135
+
136
+ ### 2. 编写执行器
137
+
138
+ ```python
139
+ # myapp/executors.py
140
+ from django_simpletask5.executors.base import BaseExecutor
141
+ from django_simpletask5.models import TaskExecution
142
+
143
+ class OrderCreateExecutor(BaseExecutor):
144
+ def execute(self, execution: TaskExecution) -> str | None:
145
+ context = execution.get_context_dict()
146
+ # 业务逻辑...
147
+ return 'ok'
148
+ ```
149
+
150
+ ### 3. 启动 Worker
151
+
152
+ 默认只监听 `default` 队列,`high_priority` 队列需要单独启动 Worker 处理。
153
+
154
+ ```bash
155
+ # 启动 4 个 Worker 处理 default 队列
156
+ python manage.py django_simpletask_executor --workers 4
157
+
158
+ # 单独启动 Worker 处理 high_priority 队列
159
+ python manage.py django_simpletask_executor --queue django_simpletask5.queue.high_priority
160
+
161
+ # 指定执行器
162
+ python manage.py django_simpletask_executor --workers 2 --service myapp.executors.OrderCreateExecutor
163
+ ```
164
+
165
+ ### 4. 启动 Cron 调度
166
+
167
+ ```bash
168
+ python manage.py django_simpletask_crontab
169
+ ```
170
+
171
+ ### 5. 触发自定义事件
172
+
173
+ ```python
174
+ order = OrderTask.objects.get(order_id='ORD-001')
175
+ order.trigger('refund', extra_context={'refund_amount': '50.00'})
176
+ ```
177
+
178
+ ## Cron 任务
179
+
180
+ ### 在代码中定义 Cron 任务
181
+
182
+ 在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob` 注册:
183
+
184
+ ```python
185
+ # myapp/cronjobs.py
186
+ from django_simpletask5.cronjob_registry import register_cronjob
187
+
188
+ register_cronjob(
189
+ name='health_check',
190
+ cron_expression='*/5 * * * *',
191
+ executor_class='django_simpletask5.executors.simple_request.SimpleRequestExecutor',
192
+ context={
193
+ 'url': 'https://example.com/health',
194
+ 'method': 'GET',
195
+ 'timeout': 10,
196
+ },
197
+ description='定期健康检查',
198
+ )
199
+ ```
200
+
201
+ ### 从代码同步到数据库
202
+
203
+ 注册的 Cron 任务需要同步到数据库才会生效。框架默认在 `django_simpletask_crontab` 启动时自动同步(可通过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = False` 关闭),也可以手动执行:
204
+
205
+ ```bash
206
+ python manage.py django_simpletask_sync_cronjobs
207
+ ```
208
+
209
+ 同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。
210
+
211
+ ## 内置执行器
212
+
213
+ | 执行器 | 说明 |
214
+ |---|---|
215
+ | `PingPongExecutor` | 健康检查,返回 `'pong'` |
216
+ | `BashScriptExecutor` | 执行 Shell 脚本 |
217
+ | `PythonScriptExecutor` | 执行 Python 代码 |
218
+ | `SimpleRequestExecutor` | 发起 HTTP 请求 |
219
+ | `StatusCheckExecutor` | 检测卡住的执行并标记超时(每 5 分钟) |
220
+ | `RetryTimeoutExecutor` | 重试超时的执行(每 10 分钟) |
221
+ | `ArchiveExecutor` | 归档已完成执行并生成统计(每天凌晨 2 点) |
222
+
223
+ ## 架构
224
+
225
+ ```
226
+ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息队列
227
+
228
+ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
229
+
230
+ 失败时自动重试,完成后归档
231
+ ```
232
+
233
+ ## Releases
234
+
235
+ ### 0.1.0
236
+
237
+ 这是 django-simpletask5 的首个正式版本。核心功能包括:
238
+
239
+ - **声明式任务模型** — 继承 `Task` 模型即可定义任务,自动处理创建/更新/删除事件
240
+ - **信号驱动自动发布** — Django 信号自动拦截模型变更,发布 `TaskExecution` 到消息队列
241
+ - **Worker 异步执行** — 多 Worker 进程消费消息队列,支持队列路由和优先级
242
+ - **Cron 定时调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
243
+ - **重试与超时** — 失败自动重试(指数退避),支持超时检测
244
+ - **加密字段** — 敏感数据自动加密存储
245
+ - **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
246
+ - **Worker 注册中心** — 基于 Redis 的 Worker 心跳与状态追踪
247
+ - **Django Admin 集成** — 完整的后台管理界面与仪表盘
248
+
249
+ ## 许可证
250
+
251
+ MIT
@@ -0,0 +1,214 @@
1
+ # django-simpletask5
2
+
3
+ > 本项目由 opencode + deepseek-v4-flash 生成
4
+
5
+ 一个轻量级的 Django 异步任务执行框架,提供声明式的任务模型、信号驱动的自动发布、Worker 进程异步执行,以及内置的 Cron 定时调度。
6
+
7
+ ## 特性
8
+
9
+ - **声明式任务模型** — 继承 `Task` 模型即可定义任务,自动处理创建/更新/删除事件
10
+ - **信号驱动** — Django 信号自动拦截模型变更,发布 `TaskExecution` 到消息队列
11
+ - **自定义事件** — 通过 `task.trigger('event_name')` 触发任意事件
12
+ - **灵活的执行器映射** — 不同事件可绑定不同的执行器类
13
+ - **队列路由** — 不同事件可路由到不同优先级队列
14
+ - **重试与超时** — 失败自动重试(指数退避),支持超时检测
15
+ - **加密字段** — 敏感数据自动加密存储
16
+ - **Cron 调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
17
+ - **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
18
+ - **Worker 注册中心** — 基于 Redis 的 Worker 心跳与状态追踪
19
+ - **Django Admin 集成** — 完整的后台管理界面
20
+
21
+ ## 依赖
22
+
23
+ - Python >= 3.8
24
+ - Django >= 3.2(兼容至 5.2.x)
25
+ - Kombu >= 5.3.0(消息队列,支持 RabbitMQ/Redis/内存)
26
+ - Redis >= 4.0.0(分布式锁、Worker 注册中心)
27
+ - 详见 `pyproject.toml`
28
+
29
+ ## 安装
30
+
31
+ ```bash
32
+ pip install django-simpletask5
33
+ ```
34
+
35
+ `django-simpletask5` 使用 [django-app-requires](https://pypi.org/project/django-app-requires/) 自动管理依赖的 app(如 `django_safe_fields`),无需手动添加到 `INSTALLED_APPS`。只需在 `settings.py` 中调用一次 patch:
36
+
37
+ ```python
38
+ from django_app_requires import patch_all as django_app_requires_patch_all
39
+ django_app_requires_patch_all()
40
+ ```
41
+
42
+ 然后在 `INSTALLED_APPS` 中添加:
43
+
44
+ ```python
45
+ INSTALLED_APPS = [
46
+ ...
47
+ 'django_simpletask5',
48
+ ]
49
+ ```
50
+
51
+ 配置分布式锁:
52
+
53
+ ```python
54
+ DJANGO_SIMPLETASK_LOCK_CONFIG = {
55
+ 'global_lock_engine_class': 'globallock.redis_global_lock.RedisGlobalLock',
56
+ 'global_lock_engine_options': {
57
+ 'host': '127.0.0.1',
58
+ 'port': 6379,
59
+ 'db': 0,
60
+ },
61
+ }
62
+ ```
63
+
64
+ 运行迁移:
65
+
66
+ ```bash
67
+ python manage.py migrate django_simpletask5
68
+ ```
69
+
70
+ ## 快速开始
71
+
72
+ ### 1. 定义任务模型
73
+
74
+ ```python
75
+ from django.db import models
76
+ from django_simpletask5.models import Task
77
+
78
+ class OrderTask(Task):
79
+ order_id = models.CharField(max_length=64, unique=True)
80
+ customer_name = models.CharField(max_length=128)
81
+ amount = models.DecimalField(max_digits=10, decimal_places=2)
82
+ status = models.CharField(max_length=32, default='pending')
83
+
84
+ executor_class = {
85
+ 'create': 'myapp.executors.OrderCreateExecutor',
86
+ 'update': 'myapp.executors.OrderUpdateExecutor',
87
+ 'delete': 'myapp.executors.OrderDeleteExecutor',
88
+ }
89
+
90
+ simpletask_queue = {
91
+ 'create': 'django_simpletask5.queue.high_priority',
92
+ 'update': 'django_simpletask5.queue.default',
93
+ 'delete': 'django_simpletask5.queue.default',
94
+ }
95
+
96
+ trigger_update_fields = ['customer_name', 'amount', 'status']
97
+ ```
98
+
99
+ ### 2. 编写执行器
100
+
101
+ ```python
102
+ # myapp/executors.py
103
+ from django_simpletask5.executors.base import BaseExecutor
104
+ from django_simpletask5.models import TaskExecution
105
+
106
+ class OrderCreateExecutor(BaseExecutor):
107
+ def execute(self, execution: TaskExecution) -> str | None:
108
+ context = execution.get_context_dict()
109
+ # 业务逻辑...
110
+ return 'ok'
111
+ ```
112
+
113
+ ### 3. 启动 Worker
114
+
115
+ 默认只监听 `default` 队列,`high_priority` 队列需要单独启动 Worker 处理。
116
+
117
+ ```bash
118
+ # 启动 4 个 Worker 处理 default 队列
119
+ python manage.py django_simpletask_executor --workers 4
120
+
121
+ # 单独启动 Worker 处理 high_priority 队列
122
+ python manage.py django_simpletask_executor --queue django_simpletask5.queue.high_priority
123
+
124
+ # 指定执行器
125
+ python manage.py django_simpletask_executor --workers 2 --service myapp.executors.OrderCreateExecutor
126
+ ```
127
+
128
+ ### 4. 启动 Cron 调度
129
+
130
+ ```bash
131
+ python manage.py django_simpletask_crontab
132
+ ```
133
+
134
+ ### 5. 触发自定义事件
135
+
136
+ ```python
137
+ order = OrderTask.objects.get(order_id='ORD-001')
138
+ order.trigger('refund', extra_context={'refund_amount': '50.00'})
139
+ ```
140
+
141
+ ## Cron 任务
142
+
143
+ ### 在代码中定义 Cron 任务
144
+
145
+ 在任意 app 的 `cronjobs.py` 文件中使用 `register_cronjob` 注册:
146
+
147
+ ```python
148
+ # myapp/cronjobs.py
149
+ from django_simpletask5.cronjob_registry import register_cronjob
150
+
151
+ register_cronjob(
152
+ name='health_check',
153
+ cron_expression='*/5 * * * *',
154
+ executor_class='django_simpletask5.executors.simple_request.SimpleRequestExecutor',
155
+ context={
156
+ 'url': 'https://example.com/health',
157
+ 'method': 'GET',
158
+ 'timeout': 10,
159
+ },
160
+ description='定期健康检查',
161
+ )
162
+ ```
163
+
164
+ ### 从代码同步到数据库
165
+
166
+ 注册的 Cron 任务需要同步到数据库才会生效。框架默认在 `django_simpletask_crontab` 启动时自动同步(可通过 `DJANGO_SIMPLETASK_CRONJOB_AUTO_SYNC = False` 关闭),也可以手动执行:
167
+
168
+ ```bash
169
+ python manage.py django_simpletask_sync_cronjobs
170
+ ```
171
+
172
+ 同步后,用户可以在 Django Admin 中查看和修改 Cron 任务,被手动修改过的任务不会在后续同步中被覆盖(`is_modified_by_user` 标记保护)。
173
+
174
+ ## 内置执行器
175
+
176
+ | 执行器 | 说明 |
177
+ |---|---|
178
+ | `PingPongExecutor` | 健康检查,返回 `'pong'` |
179
+ | `BashScriptExecutor` | 执行 Shell 脚本 |
180
+ | `PythonScriptExecutor` | 执行 Python 代码 |
181
+ | `SimpleRequestExecutor` | 发起 HTTP 请求 |
182
+ | `StatusCheckExecutor` | 检测卡住的执行并标记超时(每 5 分钟) |
183
+ | `RetryTimeoutExecutor` | 重试超时的执行(每 10 分钟) |
184
+ | `ArchiveExecutor` | 归档已完成执行并生成统计(每天凌晨 2 点) |
185
+
186
+ ## 架构
187
+
188
+ ```
189
+ Task 模型变更 → Django 信号 → 创建 TaskExecution 并发布到消息队列
190
+
191
+ Worker 消费消息 → 获取分布式锁 → 加载执行器 → 执行并保存结果
192
+
193
+ 失败时自动重试,完成后归档
194
+ ```
195
+
196
+ ## Releases
197
+
198
+ ### 0.1.0
199
+
200
+ 这是 django-simpletask5 的首个正式版本。核心功能包括:
201
+
202
+ - **声明式任务模型** — 继承 `Task` 模型即可定义任务,自动处理创建/更新/删除事件
203
+ - **信号驱动自动发布** — Django 信号自动拦截模型变更,发布 `TaskExecution` 到消息队列
204
+ - **Worker 异步执行** — 多 Worker 进程消费消息队列,支持队列路由和优先级
205
+ - **Cron 定时调度** — 内置 crontab 守护进程,支持代码注册与数据库覆盖
206
+ - **重试与超时** — 失败自动重试(指数退避),支持超时检测
207
+ - **加密字段** — 敏感数据自动加密存储
208
+ - **归档统计** — 已完成执行记录自动归档为加密 JSONL,并生成日统计
209
+ - **Worker 注册中心** — 基于 Redis 的 Worker 心跳与状态追踪
210
+ - **Django Admin 集成** — 完整的后台管理界面与仪表盘
211
+
212
+ ## 许可证
213
+
214
+ MIT
@@ -0,0 +1,7 @@
1
+ default_app_config = 'django_simpletask5.apps.DjangoSimpletask5Config'
2
+
3
+ app_requires = [
4
+ 'django_admin_dashboards',
5
+ 'django_safe_fields',
6
+ ]
7
+
@@ -0,0 +1,205 @@
1
+ import json
2
+
3
+ from django import forms
4
+ from django.contrib import admin
5
+ from django.utils.translation import gettext_lazy as _
6
+
7
+
8
+ from django_simpletask5.models import TaskExecution, CronJob, TaskExecutionStat, TaskExecutionArchive
9
+ from django_simpletask5.core.executor_scanner import (
10
+ get_executor_choices,
11
+ get_executor_schema_map,
12
+ )
13
+
14
+
15
+ class ExecutorClassWidget(forms.Select):
16
+ def __init__(self, attrs=None):
17
+ schema_map = get_executor_schema_map()
18
+ choices = [("", "---------")]
19
+ for group_name, items in get_executor_choices():
20
+ group_choices = [(v, v) for v, _ in items]
21
+ choices.append((group_name, group_choices))
22
+ attrs = attrs or {}
23
+ attrs.setdefault("class", "select2-executor")
24
+ attrs.setdefault("style", "width: 600px;")
25
+ attrs["data-schemas"] = json.dumps(schema_map, ensure_ascii=False)
26
+ super().__init__(attrs=attrs, choices=choices)
27
+
28
+
29
+ class CronJobForm(forms.ModelForm):
30
+ executor_class = forms.CharField(
31
+ label=_("Executor class"),
32
+ widget=ExecutorClassWidget(),
33
+ )
34
+ context = forms.CharField(
35
+ label=_("Context"),
36
+ required=False,
37
+ widget=forms.Textarea(
38
+ attrs={
39
+ "rows": 8,
40
+ "style": "width: 600px; font-family: monospace;",
41
+ }
42
+ ),
43
+ help_text=_("Supports JSON or YAML format. YAML is more concise and compatible with JSON standard format."),
44
+ )
45
+
46
+ def __init__(self, *args, **kwargs):
47
+ super().__init__(*args, **kwargs)
48
+ if self.instance and self.instance.pk and self.instance.context:
49
+ try:
50
+ parsed = json.loads(self.instance.context)
51
+ import yaml
52
+
53
+ self.initial["context"] = yaml.dump(
54
+ parsed,
55
+ default_flow_style=False,
56
+ allow_unicode=True,
57
+ sort_keys=False,
58
+ ).strip()
59
+ except Exception:
60
+ pass
61
+
62
+ def clean_context(self):
63
+ raw = self.cleaned_data.get("context")
64
+ if not raw:
65
+ return None
66
+ try:
67
+ parsed = json.loads(raw)
68
+ return json.dumps(parsed, ensure_ascii=False)
69
+ except json.JSONDecodeError:
70
+ pass
71
+ try:
72
+ import yaml
73
+
74
+ parsed = yaml.safe_load(raw)
75
+ if parsed is None:
76
+ return None
77
+ return json.dumps(parsed, ensure_ascii=False)
78
+ except Exception:
79
+ raise forms.ValidationError(_("Invalid parameter format. Please use JSON or YAML format."))
80
+
81
+ class Meta:
82
+ model = CronJob
83
+ fields = "__all__"
84
+
85
+ class Media:
86
+ css = {
87
+ "all": ["admin/css/vendor/select2/select2.min.css"],
88
+ }
89
+ js = [
90
+ "admin/js/vendor/jquery/jquery.min.js",
91
+ "admin/js/vendor/select2/select2.full.min.js",
92
+ "django_simpletask5/js/select2_executor.js",
93
+ "admin/js/jquery.init.js",
94
+ ]
95
+
96
+
97
+ @admin.register(TaskExecution)
98
+ class TaskExecutionAdmin(admin.ModelAdmin):
99
+ list_display = [
100
+ "execution_id",
101
+ "task_id",
102
+ "trigger_event",
103
+ "executor_class",
104
+ "status",
105
+ "retry_count",
106
+ "max_retries",
107
+ "created_at",
108
+ ]
109
+ list_filter = ["status", "trigger_event", "created_at"]
110
+ search_fields = ["execution_id", "task_id", "executor_class"]
111
+ readonly_fields = ["execution_id", "task_id", "created_at", "updated_at"]
112
+ ordering = ["-created_at"]
113
+
114
+
115
+ @admin.register(CronJob)
116
+ class CronJobAdmin(admin.ModelAdmin):
117
+ form = CronJobForm
118
+ list_display = [
119
+ "name",
120
+ "cron_expression",
121
+ "executor_class",
122
+ "is_active",
123
+ "is_modified_by_user",
124
+ "updated_at",
125
+ ]
126
+ list_filter = ["is_active", "is_modified_by_user"]
127
+ search_fields = ["name", "executor_class"]
128
+ actions = [
129
+ "reset_from_code",
130
+ "mark_as_modified",
131
+ "unmark_as_modified",
132
+ "activate_cronjobs",
133
+ "deactivate_cronjobs",
134
+ ]
135
+
136
+ def save_model(self, request, obj, form, change):
137
+ if change:
138
+ obj.is_modified_by_user = True
139
+ super().save_model(request, obj, form, change)
140
+
141
+ def save_related(self, request, form, formsets, change):
142
+ super().save_related(request, form, formsets, change)
143
+
144
+ def reset_from_code(self, request, queryset):
145
+ from django_simpletask5.core.cronjob_registry import _CRONJOB_REGISTRY
146
+
147
+ updated = 0
148
+ for cronjob in queryset:
149
+ if cronjob.name in _CRONJOB_REGISTRY:
150
+ registered = _CRONJOB_REGISTRY[cronjob.name]
151
+ cronjob.cron_expression = registered["cron_expression"]
152
+ cronjob.executor_class = registered["executor_class"]
153
+ cronjob.context = registered.get("context")
154
+ cronjob.description = registered.get("description", "")
155
+ cronjob.is_modified_by_user = False
156
+ cronjob.save()
157
+ updated += 1
158
+ self.message_user(request, f"{updated} cronjob(s) reset from code definitions.")
159
+
160
+ reset_from_code.short_description = _("Reset from code definitions")
161
+
162
+ def mark_as_modified(self, request, queryset):
163
+ updated = queryset.update(is_modified_by_user=True)
164
+ self.message_user(request, f"{updated} cronjob(s) marked as user-modified.")
165
+
166
+ mark_as_modified.short_description = _("Mark as user-modified")
167
+
168
+ def unmark_as_modified(self, request, queryset):
169
+ updated = queryset.update(is_modified_by_user=False)
170
+ self.message_user(request, f"{updated} cronjob(s) unmarked as user-modified.")
171
+
172
+ unmark_as_modified.short_description = _("Unmark as user-modified")
173
+
174
+ def activate_cronjobs(self, request, queryset):
175
+ updated = queryset.update(is_active=True)
176
+ self.message_user(request, f"{updated} cronjob(s) activated.")
177
+
178
+ activate_cronjobs.short_description = _("Activate selected tasks")
179
+
180
+ def deactivate_cronjobs(self, request, queryset):
181
+ updated = queryset.update(is_active=False)
182
+ self.message_user(request, f"{updated} cronjob(s) deactivated.")
183
+
184
+ deactivate_cronjobs.short_description = _("Deactivate selected tasks")
185
+
186
+
187
+ @admin.register(TaskExecutionStat)
188
+ class TaskExecutionStatAdmin(admin.ModelAdmin):
189
+ list_display = [
190
+ "date",
191
+ "trigger_event",
192
+ "total_count",
193
+ "success_count",
194
+ "failed_count",
195
+ "avg_duration_seconds",
196
+ ]
197
+ list_filter = ["date", "trigger_event"]
198
+ ordering = ["-date"]
199
+
200
+
201
+ @admin.register(TaskExecutionArchive)
202
+ class TaskExecutionArchiveAdmin(admin.ModelAdmin):
203
+ list_display = ["archive_date", "created_at"]
204
+ readonly_fields = ["archive_date", "file", "created_at"]
205
+ ordering = ["-archive_date"]