签入版本

This commit is contained in:
2026-09-12 18:24:41 +08:00
commit fb2b7d9538
852 changed files with 115466 additions and 0 deletions
+201
View File
@@ -0,0 +1,201 @@
# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
# +======================================================================
# | 模块: 定时任务闭环演示脚本
# | 说明: 打通「调度 → 执行 → 任务日志」完整闭环
# +======================================================================
"""
定时任务闭环演示脚本
=============================================================
打通「调度 → 执行 → 任务日志」完整闭环:
1. 向数据库新增一条测试任务(job_trigger=send_sms,job_executor 内置处理器,
仅打日志返回成功,无外部依赖)
2. 启动调度器(init_scheduler)按 cron 触发执行(execute_job)
3. 执行完成写入 fastapi_job_log(record_job_log),脚本打印新增日志证据
演示结束后测试任务置为未发布(status=0),避免后续调度器自动触发;
后台改回「运行中」并配置 cron 即可复现。
用法:
python scripts/demo_job_loop.py # cron 模式:自动等到下个整分钟边界触发一次
python scripts/demo_job_loop.py --wait 30 # cron 模式:手动等待 30s
python scripts/demo_job_loop.py --once # 立即执行一次(不走 cron 调度)
python scripts/demo_job_loop.py --cleanup # 演示后软删除测试任务及其日志
"""
import argparse
import os
import sys
import time
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src"))
# Windows 控制台默认 GBK(cp936):把 stdout/stderr 切到 UTF-8,避免中文打印乱码/崩溃
if sys.stdout and hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
# ============================================================
# 测试任务配置
# ============================================================
TEST_JOB_NAME = "闭环测试-短信发送"
TEST_JOB_TRIGGER = "send_sms" # job_executor 内置处理器,无外部依赖
# 6 字段标准 cron:首字段为秒,每 15 秒触发一次(scheduler 已支持 5/6 字段)
TEST_CRON = "*/15 * * * * *"
# ============================================================
# 辅助函数
# ============================================================
def _insert_test_job(db):
"""新增测试任务(同名已存在则复用),返回 (job, 是否新建)"""
from modules.job.models import Job
job = db.query(Job).filter(Job.job_name == TEST_JOB_NAME, Job.is_delete == 0).first()
if job:
# 复用:确保置回运行中且 cron 正确,便于重复演示
db.query(Job).filter(Job.id == job.id).update(
{"status": 1, "cron_expression": TEST_CRON}, synchronize_session=False)
db.commit()
db.refresh(job)
return job, False
job = Job(
job_name=TEST_JOB_NAME,
job_alias="闭环演示",
job_group="演示",
job_trigger=TEST_JOB_TRIGGER,
status=1, # 运行中:调度器只加载 status==1 的任务
cron_expression=TEST_CRON,
execute_policy=1, # 立即执行
is_sync=0,
note="scripts/demo_job_loop.py 闭环演示用",
)
db.add(job)
db.commit()
return job, True
def _fetch_logs(job_id):
"""查询该任务最近的任务日志(独立新会话,避免主会话 REPEATABLE READ 快照看不到新日志)"""
from core.database import SessionLocal
from modules.job_log.models import JobLog
s = SessionLocal()
try:
return s.query(JobLog).filter(
JobLog.job_id == job_id, JobLog.is_delete == 0
).order_by(JobLog.id.desc()).limit(5).all()
finally:
s.close()
# ============================================================
# 主入口
# ============================================================
def main():
parser = argparse.ArgumentParser(description="定时任务闭环演示:调度→执行→日志")
parser.add_argument("--once", action="store_true", help="立即执行一次(不依赖 cron 调度)")
parser.add_argument("--wait", type=int, default=0,
help="cron 模式轮询超时秒数;默认0=自动 25s(cron 每 15s 触发,25s 内必有新日志)")
parser.add_argument("--cleanup", action="store_true", help="演示后软删除测试任务及其日志")
args = parser.parse_args()
from core.database import SessionLocal
db = SessionLocal()
try:
job, created = _insert_test_job(db)
print(f"测试任务: id={job.id} name={job.job_name} trigger={job.job_trigger} "
f"cron={job.cron_expression}({'新建' if created else '复用已存在'})")
before = _fetch_logs(job.id)
print(f"执行前该任务日志数: {len(before)}")
if args.once:
# 立即执行:execute_job → _record_execution_log → record_job_log(同步链路)
from modules.job.scheduler import execute_job
result = execute_job(job.id, job.url, job.is_sync, job.job_trigger, {})
print(f"execute_job 返回: {result}")
else:
# 调度器闭环:启动调度器加载 cron 任务 → 轮询等待新日志出现 → 关闭
from modules.job.scheduler import init_scheduler, shutdown_scheduler
loaded = init_scheduler()
timeout = args.wait if args.wait > 0 else 25
print(f"调度器已启动,加载 {loaded} 个启用任务(含测试任务),"
f"轮询等待新日志出现(最多 {timeout}s,cron 每 15s 触发)…")
deadline = time.time() + timeout
last_count = len(before)
while time.time() < deadline:
time.sleep(2)
if len(_fetch_logs(job.id)) > last_count:
break
shutdown_scheduler()
print("调度器已关闭")
after = _fetch_logs(job.id)
before_ids = {x.id for x in before}
new_logs = [log for log in after if log.id not in before_ids]
print(f"执行后日志数: {len(after)},本次新增 {len(new_logs)} 条")
for log in new_logs:
print(f" [job_log id={log.id}] status={log.status} message={log.job_message} "
f"consume={log.consume_time}ms exception={log.exception_info or ''}")
# 收尾:置为未发布,避免演示后调度器再次自动触发
db.query(job.__class__).filter(job.__class__.id == job.id).update(
{"status": 0}, synchronize_session=False)
db.commit()
print("演示结束,测试任务已置为未发布(status=0),后台改回运行中可复现")
if args.cleanup:
from modules.job_log.models import JobLog
db.query(JobLog).filter(JobLog.job_id == job.id, JobLog.is_delete == 0).update(
{"is_delete": 1}, synchronize_session=False)
db.query(job.__class__).filter(job.__class__.id == job.id).update(
{"is_delete": 1}, synchronize_session=False)
db.commit()
print("已软删除测试任务及其日志")
finally:
db.close()
if __name__ == "__main__":
main()