adspecs 0.1.19 → 0.1.21
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.
- package/.adspecs/feature.json +16 -16
- package/.adspecs/feature.yml +28 -28
- package/.adspecs/paths.json +17 -17
- package/.adspecs/templates/04-/345/211/215/347/253/257/345/212/237/350/203/275/350/256/276/350/256/241/346/250/241/346/235/277.md +1 -1
- package/.adspecs/templates/05-/345/220/216/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +613 -724
- package/.adspecs/templates/05b-/345/211/215/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +51 -51
- package/.claude-plugin/marketplace.json +23 -23
- package/.claude-plugin/plugin.json +18 -18
- package/.qoder-plugin/plugin.json +31 -31
- package/CLAUDE.md +1 -5
- package/INSTALL.md +3 -3
- package/README.md +395 -395
- package/bin/adspecs.js +129 -129
- package/hooks/commit-queue.js +245 -245
- package/hooks/hooks.json +63 -63
- package/hooks/session-start.js +44 -44
- package/hooks/wiki-queue.js +127 -127
- package/package.json +61 -61
- package/references/ant6-front-standard/index.md +99 -99
- package/references/antd-front-demo/public/mockServiceWorker.js +361 -361
- package/references/ecp-end-standard/index.md +63 -63
- package/references/python-end-standard/01-Python/345/220/216/347/253/257/347/274/226/347/240/201/350/247/204/350/214/203.md +372 -372
- package/references/python-end-standard/02-/346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/344/270/216/344/275/277/347/224/250/350/247/204/350/214/203.md +226 -226
- package/references/python-end-standard/03-Celery/345/274/202/346/255/245/344/273/273/345/212/241/350/247/204/350/214/203.md +237 -237
- package/references/python-end-standard/04-Redis/344/275/277/347/224/250/350/247/204/350/214/203.md +231 -231
- package/scripts/postinstall.js +107 -107
- package/scripts/sync-version.js +105 -105
- package/skills/.claude/.wiki-update-queue +26 -26
- package/skills/adspecs-constitution/SKILL.md +157 -0
- package/skills/adspecs-export-word/SKILL.md +498 -498
- package/skills/adspecs-export-word/references/md-to-docx.js +862 -862
- package/skills/adspecs-export-word/references/package-lock.json +220 -220
- package/skills/adspecs-export-word/references/package.json +10 -10
- package/skills/adspecs-front-prototype/SKILL.md +405 -405
- package/skills/adspecs-front-spec/SKILL.md +4 -4
- package/skills/adspecs-front-tasks/SKILL.md +213 -173
- package/skills/adspecs-plan/SKILL.md +59 -69
- package/skills/adspecs-prd/SKILL.md +13 -5
- package/skills/adspecs-prd-to-demo/SKILL.md +532 -0
- package/skills/adspecs-tasks/SKILL.md +175 -204
- package/skills/adspecs-update-status/SKILL.md +382 -382
- package/skills/adspecs-utest/SKILL.md +107 -116
- package/skills/grill-me/SKILL.md +7 -0
- package/skills/grill-me/agents/openai.yaml +5 -0
- package/skills/playwright-cli/SKILL.md +420 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/playwright-trace/SKILL.md +171 -0
- package/skills/project-init/SKILL.md +93 -22
- package/skills/project-init/references/front-demo/.claude/settings.local.json +9 -0
- package/skills/wiki-update/SKILL.md +232 -232
- package/src/commands/doctor.js +197 -197
- package/src/commands/init.js +83 -83
- package/src/commands/plugin.js +165 -165
- package/src/commands/update.js +87 -87
- package/src/lib/area-scanner.js +129 -129
- package/src/lib/copier.js +104 -104
- package/src/lib/dir-utils.js +161 -133
- package/src/lib/json-merge.js +114 -114
- package/src/lib/paths-defaults.js +37 -37
- package/src/lib/prompts.js +428 -347
- package/src/lib/readme-gen.js +143 -143
- package/src/lib/report.js +338 -327
- package/src/lib/scaffolder.js +551 -518
- package/src/lib/short-name.js +36 -36
- package/src/utils.js +80 -80
- package/references/antd-front-demo/.env +0 -15
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
# Celery 异步任务规范
|
|
2
|
-
|
|
3
|
-
> **版本**: v1.0 | **修订日期**: 2026-07-15
|
|
4
|
-
> **技术栈**: Celery 5.4+ / Redis (Broker + Backend)
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 一、Celery 实例配置
|
|
9
|
-
|
|
10
|
-
```python
|
|
11
|
-
# app/tasks/celery_app.py
|
|
12
|
-
from celery import Celery
|
|
13
|
-
from app.core.config import settings
|
|
14
|
-
|
|
15
|
-
celery_app = Celery(
|
|
16
|
-
"worker",
|
|
17
|
-
broker=settings.CELERY_BROKER_URL,
|
|
18
|
-
backend=settings.CELERY_RESULT_BACKEND,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
celery_app.conf.update(
|
|
22
|
-
# 序列化
|
|
23
|
-
task_serializer="json",
|
|
24
|
-
accept_content=["json"],
|
|
25
|
-
result_serializer="json",
|
|
26
|
-
|
|
27
|
-
# 时区
|
|
28
|
-
timezone="Asia/Shanghai",
|
|
29
|
-
enable_utc=True,
|
|
30
|
-
|
|
31
|
-
# 任务执行
|
|
32
|
-
task_track_started=True,
|
|
33
|
-
task_time_limit=300, # 硬超时 5 分钟
|
|
34
|
-
task_soft_time_limit=240, # 软超时 4 分钟
|
|
35
|
-
|
|
36
|
-
# Worker(SQLite 场景)
|
|
37
|
-
worker_concurrency=1, # SQLite 不支持并发写
|
|
38
|
-
worker_prefetch_multiplier=1,
|
|
39
|
-
worker_pool="solo", # 单进程池
|
|
40
|
-
|
|
41
|
-
# 结果过期
|
|
42
|
-
result_expires=3600,
|
|
43
|
-
|
|
44
|
-
# Beat 定时任务
|
|
45
|
-
beat_schedule={},
|
|
46
|
-
)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 二、任务定义规范
|
|
52
|
-
|
|
53
|
-
### 2.1 命名规则
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
{action}_{entity}_task
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
示例: `sync_inventory_task`, `generate_report_task`, `send_notification_task`
|
|
60
|
-
|
|
61
|
-
### 2.2 任务定义
|
|
62
|
-
|
|
63
|
-
```python
|
|
64
|
-
# app/tasks/inventory_tasks.py
|
|
65
|
-
from app.tasks.celery_app import celery_app
|
|
66
|
-
|
|
67
|
-
@celery_app.task(
|
|
68
|
-
name="inventory.sync_inventory",
|
|
69
|
-
bind=True,
|
|
70
|
-
autoretry_for=(ConnectionError, TimeoutError),
|
|
71
|
-
max_retries=3,
|
|
72
|
-
retry_backoff=True,
|
|
73
|
-
retry_backoff_max=600,
|
|
74
|
-
)
|
|
75
|
-
def sync_inventory_task(self, warehouse_id: int, material_type: str | None = None):
|
|
76
|
-
"""同步库存数据"""
|
|
77
|
-
try:
|
|
78
|
-
# 任务逻辑(Celery 任务内使用同步数据库会话)
|
|
79
|
-
from app.core.database import SessionLocal
|
|
80
|
-
with SessionLocal() as db:
|
|
81
|
-
# ... 业务逻辑
|
|
82
|
-
db.commit()
|
|
83
|
-
except Exception as exc:
|
|
84
|
-
self.retry(exc=exc)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 2.3 参数规范
|
|
88
|
-
|
|
89
|
-
| 规则 | 说明 |
|
|
90
|
-
|----------------|------------------------------------------------|
|
|
91
|
-
| 可序列化 | 参数必须是 JSON 可序列化的(str/int/float/dict/list) |
|
|
92
|
-
| 禁止传对象 | 禁止传递 SQLAlchemy Model 实例 |
|
|
93
|
-
| 传递 ID | 通过 ID 在任务内部重新查询数据库 |
|
|
94
|
-
| 参数校验 | 任务开头进行参数校验,失败直接 raise |
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## 三、任务调用
|
|
99
|
-
|
|
100
|
-
```python
|
|
101
|
-
from app.tasks.inventory_tasks import sync_inventory_task
|
|
102
|
-
|
|
103
|
-
# 异步调用
|
|
104
|
-
result = sync_inventory_task.delay(warehouse_id=123, material_type="aluminum")
|
|
105
|
-
|
|
106
|
-
# 指定 ETA
|
|
107
|
-
from datetime import datetime, timedelta
|
|
108
|
-
result = sync_inventory_task.apply_async(
|
|
109
|
-
kwargs={"warehouse_id": 123},
|
|
110
|
-
eta=datetime.utcnow() + timedelta(minutes=10),
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
# 获取结果(同步等待)
|
|
114
|
-
result.get(timeout=60)
|
|
115
|
-
|
|
116
|
-
# 检查状态
|
|
117
|
-
result.status # PENDING / STARTED / SUCCESS / FAILURE / RETRY
|
|
118
|
-
result.ready() # bool
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## 四、错误处理与重试
|
|
124
|
-
|
|
125
|
-
```python
|
|
126
|
-
@celery_app.task(
|
|
127
|
-
bind=True,
|
|
128
|
-
autoretry_for=(ConnectionError,),
|
|
129
|
-
max_retries=3,
|
|
130
|
-
retry_backoff=True,
|
|
131
|
-
retry_backoff_max=600,
|
|
132
|
-
retry_jitter=True,
|
|
133
|
-
)
|
|
134
|
-
def process_order_task(self, order_id: int):
|
|
135
|
-
"""处理订单"""
|
|
136
|
-
try:
|
|
137
|
-
# 业务逻辑
|
|
138
|
-
...
|
|
139
|
-
except BusinessLogicError as exc:
|
|
140
|
-
# 业务错误不重试,直接记录
|
|
141
|
-
logger.error(f"Order {order_id} failed: {exc}")
|
|
142
|
-
raise
|
|
143
|
-
except ExternalServiceError as exc:
|
|
144
|
-
# 外部服务错误,手动重试
|
|
145
|
-
raise self.retry(exc=exc, countdown=60)
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
| 场景 | 策略 |
|
|
149
|
-
|----------------|----------------------|
|
|
150
|
-
| 网络超时 | autoretry + backoff |
|
|
151
|
-
| 业务逻辑错误 | 不重试,记录日志 |
|
|
152
|
-
| 数据不一致 | 不重试,告警通知 |
|
|
153
|
-
| 外部服务故障 | 手动 retry + 固定间隔|
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## 五、定时任务(Celery Beat)
|
|
158
|
-
|
|
159
|
-
```python
|
|
160
|
-
celery_app.conf.beat_schedule = {
|
|
161
|
-
"daily-inventory-sync": {
|
|
162
|
-
"task": "inventory.sync_inventory",
|
|
163
|
-
"schedule": crontab(hour=2, minute=0), # 每天凌晨 2 点
|
|
164
|
-
"kwargs": {"warehouse_id": 0},
|
|
165
|
-
},
|
|
166
|
-
"weekly-report": {
|
|
167
|
-
"task": "report.generate_weekly_report",
|
|
168
|
-
"schedule": crontab(day_of_week="monday", hour=8, minute=0),
|
|
169
|
-
},
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## 六、Worker 配置
|
|
176
|
-
|
|
177
|
-
### 6.1 启动命令
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
# 开发环境
|
|
181
|
-
celery -A app.tasks.celery_app worker --loglevel=debug --pool=solo
|
|
182
|
-
|
|
183
|
-
# 生产环境(SQLite)
|
|
184
|
-
celery -A app.tasks.celery_app worker --loglevel=info --pool=solo --concurrency=1
|
|
185
|
-
|
|
186
|
-
# Beat 定时任务调度
|
|
187
|
-
celery -A app.tasks.celery_app beat --loglevel=info
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### 6.2 SQLite 注意事项
|
|
191
|
-
|
|
192
|
-
| 事项 | 说明 |
|
|
193
|
-
|--------------|------------------------------------------------|
|
|
194
|
-
| 并发 | 必须 `--concurrency=1`,SQLite 不支持并发写 |
|
|
195
|
-
| 连接池 | 每个任务创建新的数据库连接,任务结束关闭 |
|
|
196
|
-
| 事务 | 任务内部手动 commit,不依赖外部事务 |
|
|
197
|
-
| 长时间任务 | 拆分为多个短任务,避免长时间持有数据库锁 |
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## 七、监控(Flower)
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
celery -A app.tasks.celery_app flower --port=5555
|
|
205
|
-
# 访问 http://localhost:5555
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## 八、任务测试
|
|
211
|
-
|
|
212
|
-
```python
|
|
213
|
-
# tests/conftest.py
|
|
214
|
-
@pytest.fixture(autouse=True)
|
|
215
|
-
def celery_eager_mode(monkeypatch):
|
|
216
|
-
"""测试时任务同步执行"""
|
|
217
|
-
from app.tasks.celery_app import celery_app
|
|
218
|
-
celery_app.conf.update(
|
|
219
|
-
task_always_eager=True,
|
|
220
|
-
task_eager_propagates=True,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
# tests/test_tasks/test_inventory.py
|
|
224
|
-
def test_sync_inventory_task_success(db_session):
|
|
225
|
-
"""测试库存同步任务成功"""
|
|
226
|
-
warehouse = WarehouseFactory()
|
|
227
|
-
db_session.add(warehouse)
|
|
228
|
-
db_session.commit()
|
|
229
|
-
|
|
230
|
-
result = sync_inventory_task.delay(warehouse_id=warehouse.id)
|
|
231
|
-
assert result.successful()
|
|
232
|
-
|
|
233
|
-
def test_sync_inventory_task_invalid_id():
|
|
234
|
-
"""测试无效仓库 ID"""
|
|
235
|
-
result = sync_inventory_task.delay(warehouse_id=99999)
|
|
236
|
-
assert result.failed()
|
|
237
|
-
```
|
|
1
|
+
# Celery 异步任务规范
|
|
2
|
+
|
|
3
|
+
> **版本**: v1.0 | **修订日期**: 2026-07-15
|
|
4
|
+
> **技术栈**: Celery 5.4+ / Redis (Broker + Backend)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、Celery 实例配置
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
# app/tasks/celery_app.py
|
|
12
|
+
from celery import Celery
|
|
13
|
+
from app.core.config import settings
|
|
14
|
+
|
|
15
|
+
celery_app = Celery(
|
|
16
|
+
"worker",
|
|
17
|
+
broker=settings.CELERY_BROKER_URL,
|
|
18
|
+
backend=settings.CELERY_RESULT_BACKEND,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
celery_app.conf.update(
|
|
22
|
+
# 序列化
|
|
23
|
+
task_serializer="json",
|
|
24
|
+
accept_content=["json"],
|
|
25
|
+
result_serializer="json",
|
|
26
|
+
|
|
27
|
+
# 时区
|
|
28
|
+
timezone="Asia/Shanghai",
|
|
29
|
+
enable_utc=True,
|
|
30
|
+
|
|
31
|
+
# 任务执行
|
|
32
|
+
task_track_started=True,
|
|
33
|
+
task_time_limit=300, # 硬超时 5 分钟
|
|
34
|
+
task_soft_time_limit=240, # 软超时 4 分钟
|
|
35
|
+
|
|
36
|
+
# Worker(SQLite 场景)
|
|
37
|
+
worker_concurrency=1, # SQLite 不支持并发写
|
|
38
|
+
worker_prefetch_multiplier=1,
|
|
39
|
+
worker_pool="solo", # 单进程池
|
|
40
|
+
|
|
41
|
+
# 结果过期
|
|
42
|
+
result_expires=3600,
|
|
43
|
+
|
|
44
|
+
# Beat 定时任务
|
|
45
|
+
beat_schedule={},
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 二、任务定义规范
|
|
52
|
+
|
|
53
|
+
### 2.1 命名规则
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
{action}_{entity}_task
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
示例: `sync_inventory_task`, `generate_report_task`, `send_notification_task`
|
|
60
|
+
|
|
61
|
+
### 2.2 任务定义
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# app/tasks/inventory_tasks.py
|
|
65
|
+
from app.tasks.celery_app import celery_app
|
|
66
|
+
|
|
67
|
+
@celery_app.task(
|
|
68
|
+
name="inventory.sync_inventory",
|
|
69
|
+
bind=True,
|
|
70
|
+
autoretry_for=(ConnectionError, TimeoutError),
|
|
71
|
+
max_retries=3,
|
|
72
|
+
retry_backoff=True,
|
|
73
|
+
retry_backoff_max=600,
|
|
74
|
+
)
|
|
75
|
+
def sync_inventory_task(self, warehouse_id: int, material_type: str | None = None):
|
|
76
|
+
"""同步库存数据"""
|
|
77
|
+
try:
|
|
78
|
+
# 任务逻辑(Celery 任务内使用同步数据库会话)
|
|
79
|
+
from app.core.database import SessionLocal
|
|
80
|
+
with SessionLocal() as db:
|
|
81
|
+
# ... 业务逻辑
|
|
82
|
+
db.commit()
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
self.retry(exc=exc)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2.3 参数规范
|
|
88
|
+
|
|
89
|
+
| 规则 | 说明 |
|
|
90
|
+
|----------------|------------------------------------------------|
|
|
91
|
+
| 可序列化 | 参数必须是 JSON 可序列化的(str/int/float/dict/list) |
|
|
92
|
+
| 禁止传对象 | 禁止传递 SQLAlchemy Model 实例 |
|
|
93
|
+
| 传递 ID | 通过 ID 在任务内部重新查询数据库 |
|
|
94
|
+
| 参数校验 | 任务开头进行参数校验,失败直接 raise |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 三、任务调用
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from app.tasks.inventory_tasks import sync_inventory_task
|
|
102
|
+
|
|
103
|
+
# 异步调用
|
|
104
|
+
result = sync_inventory_task.delay(warehouse_id=123, material_type="aluminum")
|
|
105
|
+
|
|
106
|
+
# 指定 ETA
|
|
107
|
+
from datetime import datetime, timedelta
|
|
108
|
+
result = sync_inventory_task.apply_async(
|
|
109
|
+
kwargs={"warehouse_id": 123},
|
|
110
|
+
eta=datetime.utcnow() + timedelta(minutes=10),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# 获取结果(同步等待)
|
|
114
|
+
result.get(timeout=60)
|
|
115
|
+
|
|
116
|
+
# 检查状态
|
|
117
|
+
result.status # PENDING / STARTED / SUCCESS / FAILURE / RETRY
|
|
118
|
+
result.ready() # bool
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 四、错误处理与重试
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
@celery_app.task(
|
|
127
|
+
bind=True,
|
|
128
|
+
autoretry_for=(ConnectionError,),
|
|
129
|
+
max_retries=3,
|
|
130
|
+
retry_backoff=True,
|
|
131
|
+
retry_backoff_max=600,
|
|
132
|
+
retry_jitter=True,
|
|
133
|
+
)
|
|
134
|
+
def process_order_task(self, order_id: int):
|
|
135
|
+
"""处理订单"""
|
|
136
|
+
try:
|
|
137
|
+
# 业务逻辑
|
|
138
|
+
...
|
|
139
|
+
except BusinessLogicError as exc:
|
|
140
|
+
# 业务错误不重试,直接记录
|
|
141
|
+
logger.error(f"Order {order_id} failed: {exc}")
|
|
142
|
+
raise
|
|
143
|
+
except ExternalServiceError as exc:
|
|
144
|
+
# 外部服务错误,手动重试
|
|
145
|
+
raise self.retry(exc=exc, countdown=60)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| 场景 | 策略 |
|
|
149
|
+
|----------------|----------------------|
|
|
150
|
+
| 网络超时 | autoretry + backoff |
|
|
151
|
+
| 业务逻辑错误 | 不重试,记录日志 |
|
|
152
|
+
| 数据不一致 | 不重试,告警通知 |
|
|
153
|
+
| 外部服务故障 | 手动 retry + 固定间隔|
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 五、定时任务(Celery Beat)
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
celery_app.conf.beat_schedule = {
|
|
161
|
+
"daily-inventory-sync": {
|
|
162
|
+
"task": "inventory.sync_inventory",
|
|
163
|
+
"schedule": crontab(hour=2, minute=0), # 每天凌晨 2 点
|
|
164
|
+
"kwargs": {"warehouse_id": 0},
|
|
165
|
+
},
|
|
166
|
+
"weekly-report": {
|
|
167
|
+
"task": "report.generate_weekly_report",
|
|
168
|
+
"schedule": crontab(day_of_week="monday", hour=8, minute=0),
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 六、Worker 配置
|
|
176
|
+
|
|
177
|
+
### 6.1 启动命令
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# 开发环境
|
|
181
|
+
celery -A app.tasks.celery_app worker --loglevel=debug --pool=solo
|
|
182
|
+
|
|
183
|
+
# 生产环境(SQLite)
|
|
184
|
+
celery -A app.tasks.celery_app worker --loglevel=info --pool=solo --concurrency=1
|
|
185
|
+
|
|
186
|
+
# Beat 定时任务调度
|
|
187
|
+
celery -A app.tasks.celery_app beat --loglevel=info
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 6.2 SQLite 注意事项
|
|
191
|
+
|
|
192
|
+
| 事项 | 说明 |
|
|
193
|
+
|--------------|------------------------------------------------|
|
|
194
|
+
| 并发 | 必须 `--concurrency=1`,SQLite 不支持并发写 |
|
|
195
|
+
| 连接池 | 每个任务创建新的数据库连接,任务结束关闭 |
|
|
196
|
+
| 事务 | 任务内部手动 commit,不依赖外部事务 |
|
|
197
|
+
| 长时间任务 | 拆分为多个短任务,避免长时间持有数据库锁 |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 七、监控(Flower)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
celery -A app.tasks.celery_app flower --port=5555
|
|
205
|
+
# 访问 http://localhost:5555
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 八、任务测试
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# tests/conftest.py
|
|
214
|
+
@pytest.fixture(autouse=True)
|
|
215
|
+
def celery_eager_mode(monkeypatch):
|
|
216
|
+
"""测试时任务同步执行"""
|
|
217
|
+
from app.tasks.celery_app import celery_app
|
|
218
|
+
celery_app.conf.update(
|
|
219
|
+
task_always_eager=True,
|
|
220
|
+
task_eager_propagates=True,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# tests/test_tasks/test_inventory.py
|
|
224
|
+
def test_sync_inventory_task_success(db_session):
|
|
225
|
+
"""测试库存同步任务成功"""
|
|
226
|
+
warehouse = WarehouseFactory()
|
|
227
|
+
db_session.add(warehouse)
|
|
228
|
+
db_session.commit()
|
|
229
|
+
|
|
230
|
+
result = sync_inventory_task.delay(warehouse_id=warehouse.id)
|
|
231
|
+
assert result.successful()
|
|
232
|
+
|
|
233
|
+
def test_sync_inventory_task_invalid_id():
|
|
234
|
+
"""测试无效仓库 ID"""
|
|
235
|
+
result = sync_inventory_task.delay(warehouse_id=99999)
|
|
236
|
+
assert result.failed()
|
|
237
|
+
```
|