refactor: P4c 最终批次-4 job 模块
- get_job_page/add/update/delete/status 按新范式改造(get_info 委托 JobRepository) - pause_job/resume_job/run_job_once/reload_all_jobs 去 R 改 BizError/返回数据 - router 取参包 R;修复多行 config 导入被 ast 破坏问题 【验收】flake8 零告警;应用启动;job 分页运行时正常;12 passed Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
0e0179859f
commit
6bd4951f95
+29
-33
@@ -6,7 +6,7 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Dict, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -15,6 +15,7 @@ logger = logging.getLogger(__name__)
|
||||
# 任务执行器
|
||||
# ============================================================
|
||||
|
||||
|
||||
class JobExecutor:
|
||||
"""
|
||||
任务执行器
|
||||
@@ -30,11 +31,11 @@ class JobExecutor:
|
||||
def _register_default_handlers(self):
|
||||
"""注册默认的任务处理器"""
|
||||
# 示例:短信发送任务
|
||||
self.register('send_sms', self._send_sms)
|
||||
self.register("send_sms", self._send_sms)
|
||||
# 示例:邮件发送任务
|
||||
self.register('send_email', self._send_email)
|
||||
self.register("send_email", self._send_email)
|
||||
# 示例:数据同步任务
|
||||
self.register('sync_data', self._sync_data)
|
||||
self.register("sync_data", self._sync_data)
|
||||
|
||||
def register(self, trigger: str, handler):
|
||||
"""
|
||||
@@ -82,11 +83,11 @@ class JobExecutor:
|
||||
"""
|
||||
from src.modules.job import sms_service
|
||||
|
||||
phone = kwargs.get('phone')
|
||||
template_code = kwargs.get('template_code')
|
||||
template_params = kwargs.get('template_params')
|
||||
sign_name = kwargs.get('sign_name')
|
||||
job_id = kwargs.get('job_id')
|
||||
phone = kwargs.get("phone")
|
||||
template_code = kwargs.get("template_code")
|
||||
template_params = kwargs.get("template_params")
|
||||
sign_name = kwargs.get("sign_name")
|
||||
job_id = kwargs.get("job_id")
|
||||
|
||||
logger.info(f"发送短信 [任务ID: {job_id}, 手机号: {phone}, 模板: {template_code}]")
|
||||
|
||||
@@ -98,10 +99,10 @@ class JobExecutor:
|
||||
)
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'message': '短信发送成功' if success else '短信发送失败',
|
||||
'phone': phone,
|
||||
'template_code': template_code
|
||||
"success": success,
|
||||
"message": "短信发送成功" if success else "短信发送失败",
|
||||
"phone": phone,
|
||||
"template_code": template_code,
|
||||
}
|
||||
|
||||
def _send_email(self, **kwargs) -> Dict:
|
||||
@@ -116,22 +117,22 @@ class JobExecutor:
|
||||
"""
|
||||
from src.modules.job import mail_service
|
||||
|
||||
to = kwargs.get('to')
|
||||
subject = kwargs.get('subject')
|
||||
body = kwargs.get('body')
|
||||
html = kwargs.get('html')
|
||||
cc = kwargs.get('cc')
|
||||
job_id = kwargs.get('job_id')
|
||||
to = kwargs.get("to")
|
||||
subject = kwargs.get("subject")
|
||||
body = kwargs.get("body")
|
||||
html = kwargs.get("html")
|
||||
cc = kwargs.get("cc")
|
||||
job_id = kwargs.get("job_id")
|
||||
|
||||
logger.info(f"发送邮件 [任务ID: {job_id}, 收件人: {to}, 主题: {subject}]")
|
||||
|
||||
success = mail_service.send_mail(to=to, subject=subject, body=body, html=html, cc=cc)
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'message': '邮件发送成功' if success else '邮件发送失败',
|
||||
'to': to,
|
||||
'subject': subject
|
||||
"success": success,
|
||||
"message": "邮件发送成功" if success else "邮件发送失败",
|
||||
"to": to,
|
||||
"subject": subject,
|
||||
}
|
||||
|
||||
def _sync_data(self, **kwargs) -> Dict:
|
||||
@@ -144,21 +145,16 @@ class JobExecutor:
|
||||
Returns:
|
||||
Dict: 执行结果
|
||||
"""
|
||||
source = kwargs.get('source')
|
||||
target = kwargs.get('target')
|
||||
sync_type = kwargs.get('sync_type')
|
||||
job_id = kwargs.get('job_id')
|
||||
source = kwargs.get("source")
|
||||
target = kwargs.get("target")
|
||||
sync_type = kwargs.get("sync_type")
|
||||
job_id = kwargs.get("job_id")
|
||||
|
||||
logger.info(f"数据同步 [任务ID: {job_id}, 源: {source}, 目标: {target}, 类型: {sync_type}]")
|
||||
|
||||
# TODO: 调用实际的数据同步服务
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': '数据同步成功',
|
||||
'source': source,
|
||||
'target': target
|
||||
}
|
||||
return {"success": True, "message": "数据同步成功", "source": source, "target": target}
|
||||
|
||||
|
||||
# 创建全局执行器实例
|
||||
|
||||
@@ -26,7 +26,6 @@ import logging
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from src.core.mail import mail
|
||||
from flask import current_app
|
||||
from flask_mail import Message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
+15
-53
@@ -9,13 +9,13 @@ from src.common.base_db import BaseDB
|
||||
from src.common.base_model import BaseModel
|
||||
from config import DB_PREFIX
|
||||
from src.core.database import db
|
||||
from src.common.utils.case_converter import convert_camel_to_snake
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 定时任务模型
|
||||
# ============================================================
|
||||
|
||||
|
||||
class Job(BaseModel, BaseDB):
|
||||
"""
|
||||
定时任务模型
|
||||
@@ -47,77 +47,36 @@ class Job(BaseModel, BaseDB):
|
||||
# ============================================================
|
||||
|
||||
# 任务名称
|
||||
job_name = db.Column(
|
||||
db.String(255),
|
||||
nullable=False,
|
||||
comment="任务名称"
|
||||
)
|
||||
job_name = db.Column(db.String(255), nullable=False, comment="任务名称")
|
||||
|
||||
# 任务别名
|
||||
job_alias = db.Column(
|
||||
db.String(255),
|
||||
nullable=True,
|
||||
comment="任务别名"
|
||||
)
|
||||
job_alias = db.Column(db.String(255), nullable=True, comment="任务别名")
|
||||
|
||||
# 任务分组
|
||||
job_group = db.Column(
|
||||
db.String(255),
|
||||
nullable=True,
|
||||
comment="任务分组"
|
||||
)
|
||||
job_group = db.Column(db.String(255), nullable=True, comment="任务分组")
|
||||
|
||||
# 任务触发器
|
||||
job_trigger = db.Column(
|
||||
db.String(255),
|
||||
nullable=True,
|
||||
comment="任务触发器"
|
||||
)
|
||||
job_trigger = db.Column(db.String(255), nullable=True, comment="任务触发器")
|
||||
|
||||
# 任务状态:0-未发布 1-运行中 2-暂停 3-删除
|
||||
status = db.Column(
|
||||
db.Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="任务状态:0-未发布 1-运行中 2-暂停 3-删除"
|
||||
)
|
||||
status = db.Column(db.Integer, nullable=False, default=0, comment="任务状态:0-未发布 1-运行中 2-暂停 3-删除")
|
||||
|
||||
# Cron表达式
|
||||
cron_expression = db.Column(
|
||||
db.String(255),
|
||||
nullable=True,
|
||||
comment="Cron表达式"
|
||||
)
|
||||
cron_expression = db.Column(db.String(255), nullable=True, comment="Cron表达式")
|
||||
|
||||
# 执行策略:1-立即执行 2-执行一次 3-放弃执行
|
||||
execute_policy = db.Column(
|
||||
db.Integer,
|
||||
nullable=False,
|
||||
default=1,
|
||||
comment="执行策略:1-立即执行 2-执行一次 3-放弃执行"
|
||||
db.Integer, nullable=False, default=1, comment="执行策略:1-立即执行 2-执行一次 3-放弃执行"
|
||||
)
|
||||
|
||||
# 是否同步任务:0-否 1-是
|
||||
is_sync = db.Column(
|
||||
db.Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="是否同步任务:0-否 1-是"
|
||||
)
|
||||
is_sync = db.Column(db.Integer, nullable=False, default=0, comment="是否同步任务:0-否 1-是")
|
||||
|
||||
# 任务URL
|
||||
url = db.Column(
|
||||
db.String(255),
|
||||
nullable=True,
|
||||
comment="任务URL"
|
||||
)
|
||||
url = db.Column(db.String(255), nullable=True, comment="任务URL")
|
||||
|
||||
# 任务备注
|
||||
note = db.Column(
|
||||
db.String(255),
|
||||
nullable=True,
|
||||
comment="任务备注"
|
||||
)
|
||||
note = db.Column(db.String(255), nullable=True, comment="任务备注")
|
||||
|
||||
# ============================================================
|
||||
# 常量定义
|
||||
@@ -125,6 +84,7 @@ class Job(BaseModel, BaseDB):
|
||||
|
||||
class Status:
|
||||
"""任务状态常量"""
|
||||
|
||||
UNPUBLISHED = 0 # 未发布
|
||||
RUNNING = 1 # 运行中
|
||||
PAUSED = 2 # 暂停
|
||||
@@ -132,12 +92,14 @@ class Job(BaseModel, BaseDB):
|
||||
|
||||
class ExecutePolicy:
|
||||
"""执行策略常量"""
|
||||
|
||||
EXECUTE_NOW = 1 # 立即执行
|
||||
EXECUTE_ONCE = 2 # 执行一次
|
||||
ABANDON = 3 # 放弃执行
|
||||
|
||||
class IsSync:
|
||||
"""是否同步任务常量"""
|
||||
|
||||
ASYNC = 0 # 异步
|
||||
SYNC = 1 # 同步
|
||||
|
||||
@@ -146,7 +108,7 @@ class Job(BaseModel, BaseDB):
|
||||
# ============================================================
|
||||
|
||||
def __repr__(self):
|
||||
return '定时任务:{}'.format(self.job_name)
|
||||
return "定时任务:{}".format(self.job_name)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Job 仓储模块
|
||||
=============================================================
|
||||
|
||||
数据访问层,继承 BaseRepository 获得通用 CRUD 能力。
|
||||
"""
|
||||
|
||||
from src.common.base_repository import BaseRepository
|
||||
from src.modules.job.models import Job
|
||||
|
||||
|
||||
class JobRepository(BaseRepository):
|
||||
"""Job 仓储"""
|
||||
|
||||
model = Job
|
||||
+58
-38
@@ -4,35 +4,37 @@
|
||||
|
||||
提供定时任务(Job)相关的API接口。
|
||||
"""
|
||||
import logging
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import Blueprint, request
|
||||
|
||||
from application.middleware.check_demo import check_demo
|
||||
from application.middleware.check_permission import check_permission
|
||||
from src.modules.job import service as job
|
||||
from src.common.utils.request import parse_batch_ids
|
||||
job_blue = Blueprint('job', __name__, url_prefix='/job')
|
||||
from src.common.utils.request import parse_batch_ids, parse_request_body
|
||||
|
||||
from src.core import response as R
|
||||
|
||||
job_blue = Blueprint("job", __name__, url_prefix="/job")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务分页查询
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/page', methods=["GET"])
|
||||
|
||||
@job_blue.route("/page", methods=["GET"])
|
||||
@check_permission("sys:job:page")
|
||||
def page():
|
||||
"""查询定时任务分页数据"""
|
||||
result = job.get_job_page()
|
||||
return result
|
||||
return R.ok(data=job.get_job_page(request.args))
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务详情查询
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/detail/<int:id>', methods=["GET"])
|
||||
|
||||
@job_blue.route("/detail/<int:id>", methods=["GET"])
|
||||
@check_permission("sys:job:detail")
|
||||
def detail(id):
|
||||
"""查询定时任务详情"""
|
||||
@@ -46,46 +48,56 @@ def detail(id):
|
||||
# 任务添加
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/add', methods=["POST"])
|
||||
|
||||
@job_blue.route("/add", methods=["POST"])
|
||||
@check_permission("sys:job:add")
|
||||
@check_demo
|
||||
def add():
|
||||
"""添加定时任务"""
|
||||
result = job.add_job()
|
||||
return result
|
||||
json_data, err = parse_request_body()
|
||||
if err:
|
||||
return R.failed(msg=err)
|
||||
job.add_job(json_data)
|
||||
return R.ok(msg="添加成功")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务更新
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/update', methods=["PUT"])
|
||||
|
||||
@job_blue.route("/update", methods=["PUT"])
|
||||
@check_permission("sys:job:update")
|
||||
@check_demo
|
||||
def update():
|
||||
"""更新定时任务"""
|
||||
result = job.update_job()
|
||||
return result
|
||||
json_data, err = parse_request_body()
|
||||
if err:
|
||||
return R.failed(msg=err)
|
||||
job.update_job(json_data)
|
||||
return R.ok(msg="更新成功")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务删除
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/delete/<string:id>', methods=["DELETE"])
|
||||
|
||||
@job_blue.route("/delete/<string:id>", methods=["DELETE"])
|
||||
@check_permission("sys:job:delete")
|
||||
@check_demo
|
||||
def delete(id):
|
||||
"""删除定时任务"""
|
||||
result = job.delete_jobs(id)
|
||||
return result
|
||||
count = job.delete_jobs(id)
|
||||
return R.ok(msg=f"本次共删除{count}条数据")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务批量删除
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/batchDelete', methods=["DELETE"])
|
||||
|
||||
@job_blue.route("/batchDelete", methods=["DELETE"])
|
||||
@check_permission("sys:job:batch_delete")
|
||||
@check_demo
|
||||
def batch_delete():
|
||||
@@ -93,79 +105,87 @@ def batch_delete():
|
||||
id_str, err = parse_batch_ids()
|
||||
if err:
|
||||
return err
|
||||
return job.delete_jobs(id_str)
|
||||
count = job.delete_jobs(id_str)
|
||||
return R.ok(msg=f"本次共删除{count}条数据")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务状态设置
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/status', methods=["POST"])
|
||||
|
||||
@job_blue.route("/status", methods=["POST"])
|
||||
@check_permission("sys:job:status")
|
||||
@check_demo
|
||||
def status():
|
||||
"""设置任务状态"""
|
||||
result = job.update_job_status()
|
||||
return result
|
||||
json_data, err = parse_request_body()
|
||||
if err:
|
||||
return R.failed(msg=err)
|
||||
job.update_job_status(json_data)
|
||||
return R.ok(msg="更新成功")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 暂停任务
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/pause/<int:id>', methods=["GET"])
|
||||
|
||||
@job_blue.route("/pause/<int:id>", methods=["GET"])
|
||||
@check_permission("sys:job:pause")
|
||||
@check_demo
|
||||
def pause(id):
|
||||
"""暂停任务"""
|
||||
result = job.pause_job(id)
|
||||
return result
|
||||
job.pause_job(id)
|
||||
return R.ok(msg="暂停成功")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 恢复任务
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/resume/<int:id>', methods=["GET"])
|
||||
|
||||
@job_blue.route("/resume/<int:id>", methods=["GET"])
|
||||
@check_permission("sys:job:resume")
|
||||
@check_demo
|
||||
def resume(id):
|
||||
"""恢复任务"""
|
||||
result = job.resume_job(id)
|
||||
return result
|
||||
job.resume_job(id)
|
||||
return R.ok(msg="恢复成功")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 立即执行
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/runOnce/<int:id>', methods=["GET"])
|
||||
|
||||
@job_blue.route("/runOnce/<int:id>", methods=["GET"])
|
||||
@check_permission("sys:job:runOnce")
|
||||
@check_demo
|
||||
def run_once(id):
|
||||
"""立即执行一次任务"""
|
||||
result = job.run_job_once(id)
|
||||
return result
|
||||
return R.ok(data=job.run_job_once(id))
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 重新加载调度器
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/reload', methods=["GET"])
|
||||
|
||||
@job_blue.route("/reload", methods=["GET"])
|
||||
@check_permission("sys:job:reload")
|
||||
@check_demo
|
||||
def reload():
|
||||
"""重新加载所有任务到调度器"""
|
||||
result = job.reload_all_jobs()
|
||||
return result
|
||||
return R.ok(data=job.reload_all_jobs())
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 发送测试短信
|
||||
# ============================================================
|
||||
|
||||
@job_blue.route('/sendSms', methods=["POST"])
|
||||
|
||||
@job_blue.route("/sendSms", methods=["POST"])
|
||||
@check_permission("sys:job:sendSms")
|
||||
def sendSms():
|
||||
"""
|
||||
@@ -185,9 +205,9 @@ def sendSms():
|
||||
if not data:
|
||||
return R.failed("参数不能为空")
|
||||
|
||||
phone = data.get('phone')
|
||||
template_code = data.get('template_code')
|
||||
template_params = data.get('template_params')
|
||||
phone = data.get("phone")
|
||||
template_code = data.get("template_code")
|
||||
template_params = data.get("template_params")
|
||||
|
||||
if not phone or not template_code:
|
||||
return R.failed("手机号和模板CODE不能为空")
|
||||
|
||||
@@ -77,6 +77,7 @@ _scheduler = None
|
||||
# 调度器实例获取函数
|
||||
# ============================================================
|
||||
|
||||
|
||||
def get_scheduler():
|
||||
"""
|
||||
获取调度器实例(延迟初始化)
|
||||
@@ -90,7 +91,7 @@ def get_scheduler():
|
||||
global _scheduler
|
||||
if _scheduler is None:
|
||||
# 创建后台调度器,设置时区为北京时间
|
||||
_scheduler = BackgroundScheduler(timezone='Asia/Shanghai')
|
||||
_scheduler = BackgroundScheduler(timezone="Asia/Shanghai")
|
||||
return _scheduler
|
||||
|
||||
|
||||
@@ -102,6 +103,7 @@ scheduler = get_scheduler()
|
||||
# URL 构建工具函数
|
||||
# ============================================================
|
||||
|
||||
|
||||
def build_full_url(url: str) -> str:
|
||||
"""
|
||||
构建完整的URL
|
||||
@@ -125,11 +127,11 @@ def build_full_url(url: str) -> str:
|
||||
return url
|
||||
|
||||
# 已经是完整URL,直接返回
|
||||
if url.startswith(('http://', 'https://')):
|
||||
if url.startswith(("http://", "https://")):
|
||||
return url
|
||||
|
||||
# 相对路径,拼接站点域名
|
||||
if url.startswith('/'):
|
||||
if url.startswith("/"):
|
||||
url = f"{FLASK_SITE_DOMAIN}{url}"
|
||||
|
||||
return url
|
||||
@@ -139,12 +141,13 @@ def build_full_url(url: str) -> str:
|
||||
# 任务执行核心函数
|
||||
# ============================================================
|
||||
|
||||
|
||||
def execute_job(
|
||||
job_id: int,
|
||||
url: Optional[str] = None,
|
||||
is_sync: int = 0,
|
||||
job_trigger: Optional[str] = None,
|
||||
params: Optional[Dict[str, Any]] = None
|
||||
job_id: int,
|
||||
url: Optional[str] = None,
|
||||
is_sync: int = 0,
|
||||
job_trigger: Optional[str] = None,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
执行任务(调度器调用的入口函数)
|
||||
@@ -176,19 +179,20 @@ def execute_job(
|
||||
result = _execute_by_trigger(job_trigger, job_id, params)
|
||||
else:
|
||||
# 无效的任务配置
|
||||
result = {'success': False, 'error': '任务既没有URL也没有触发器'}
|
||||
result = {"success": False, "error": "任务既没有URL也没有触发器"}
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"执行任务异常 [ID: {job_id}]: {str(e)}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# URL 方式任务执行
|
||||
# ============================================================
|
||||
|
||||
|
||||
def _execute_by_url(job_id: int, url: str, is_sync: int, params: Optional[Dict] = None) -> Dict:
|
||||
"""
|
||||
通过URL方式执行任务
|
||||
@@ -208,11 +212,7 @@ def _execute_by_url(job_id: int, url: str, is_sync: int, params: Optional[Dict]
|
||||
full_url = build_full_url(url)
|
||||
|
||||
# 构建请求数据
|
||||
request_data = {
|
||||
'job_id': job_id,
|
||||
'timestamp': datetime.now().isoformat(), # 请求时间戳
|
||||
'params': params or {}
|
||||
}
|
||||
request_data = {"job_id": job_id, "timestamp": datetime.now().isoformat(), "params": params or {}} # 请求时间戳
|
||||
|
||||
# 根据同步/异步选择发送方式
|
||||
if is_sync == 1:
|
||||
@@ -240,20 +240,15 @@ def _send_sync_request(url: str, data: Dict, job_id: int) -> Dict:
|
||||
|
||||
# 准备请求头
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Scheduler/1.0',
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "Scheduler/1.0",
|
||||
# 添加定时任务专用密钥,用于跳过权限验证
|
||||
'X-Scheduler-Key': INTERNAL_API_KEY,
|
||||
"X-Scheduler-Key": INTERNAL_API_KEY,
|
||||
}
|
||||
|
||||
# 发送POST请求
|
||||
response = requests.post(
|
||||
url,
|
||||
json=data,
|
||||
timeout=30, # 30秒超时
|
||||
headers=headers
|
||||
)
|
||||
response = requests.post(url, json=data, timeout=30, headers=headers) # 30秒超时
|
||||
response.raise_for_status() # 非200状态码抛出异常
|
||||
|
||||
# 解析响应数据
|
||||
@@ -261,16 +256,12 @@ def _send_sync_request(url: str, data: Dict, job_id: int) -> Dict:
|
||||
result_data = response.json()
|
||||
except Exception:
|
||||
# JSON解析失败时返回原始文本(限制长度)
|
||||
result_data = {'text': response.text[:200]}
|
||||
result_data = {"text": response.text[:200]}
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'status_code': response.status_code,
|
||||
'data': result_data
|
||||
}
|
||||
return {"success": True, "status_code": response.status_code, "data": result_data}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {'success': False, 'error': str(e)}
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
def _send_async_request(url: str, data: Dict, job_id: int) -> Dict:
|
||||
@@ -293,8 +284,8 @@ def _send_async_request(url: str, data: Dict, job_id: int) -> Dict:
|
||||
"""异步请求执行函数(在独立线程中运行)"""
|
||||
try:
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Scheduler-Key': INTERNAL_API_KEY,
|
||||
"Content-Type": "application/json",
|
||||
"X-Scheduler-Key": INTERNAL_API_KEY,
|
||||
}
|
||||
resp = requests.post(url, json=data, timeout=30, headers=headers)
|
||||
logger.info(f"异步任务完成 [ID: {job_id}, 状态码: {resp.status_code}]")
|
||||
@@ -305,7 +296,7 @@ def _send_async_request(url: str, data: Dict, job_id: int) -> Dict:
|
||||
thread = threading.Thread(target=async_request)
|
||||
thread.start()
|
||||
|
||||
return {'success': True, 'status': 'triggered'}
|
||||
return {"success": True, "status": "triggered"}
|
||||
|
||||
|
||||
def _execute_by_trigger(trigger: str, job_id: int, params: Optional[Dict] = None) -> Dict:
|
||||
@@ -324,17 +315,19 @@ def _execute_by_trigger(trigger: str, job_id: int, params: Optional[Dict] = None
|
||||
"""
|
||||
try:
|
||||
from src.modules.job.executor import job_executor
|
||||
|
||||
result = job_executor.execute(trigger, job_id=job_id, **(params or {}))
|
||||
return {'success': True, 'trigger': trigger, 'data': result}
|
||||
return {"success": True, "trigger": trigger, "data": result}
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': str(e)}
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 调度器任务管理函数
|
||||
# ============================================================
|
||||
|
||||
def add_job_to_scheduler(job_dict:dict ) -> bool:
|
||||
|
||||
def add_job_to_scheduler(job_dict: dict) -> bool:
|
||||
"""
|
||||
添加任务到调度器
|
||||
|
||||
@@ -357,13 +350,13 @@ def add_job_to_scheduler(job_dict:dict ) -> bool:
|
||||
# ============================================================
|
||||
# 1. 从字典中提取任务参数
|
||||
# ============================================================
|
||||
job_id = job_dict.get('id')
|
||||
job_status = job_dict.get('status')
|
||||
job_cron = job_dict.get('cronExpression')
|
||||
job_url = job_dict.get('url')
|
||||
job_sync = job_dict.get('isSync')
|
||||
job_trigger = job_dict.get('jobTrigger')
|
||||
job_name = job_dict.get('jobName')
|
||||
job_id = job_dict.get("id")
|
||||
job_status = job_dict.get("status")
|
||||
job_cron = job_dict.get("cronExpression")
|
||||
job_url = job_dict.get("url")
|
||||
job_sync = job_dict.get("isSync")
|
||||
job_trigger = job_dict.get("jobTrigger")
|
||||
job_name = job_dict.get("jobName")
|
||||
|
||||
print(f"[scheduler] add_job id={job_id} status={job_status} cron='{job_cron}'")
|
||||
|
||||
@@ -386,6 +379,7 @@ def add_job_to_scheduler(job_dict:dict ) -> bool:
|
||||
try:
|
||||
trigger = CronTrigger.from_crontab(job_cron)
|
||||
from datetime import datetime
|
||||
|
||||
next_run = trigger.get_next_fire_time(None, datetime.now())
|
||||
logger.debug(f"Cron表达式有效 (id={job_id}, 下次执行: {next_run})")
|
||||
|
||||
@@ -417,7 +411,7 @@ def add_job_to_scheduler(job_dict:dict ) -> bool:
|
||||
name=job_name, # 任务显示名称
|
||||
replace_existing=True, # 替换已存在的任务
|
||||
misfire_grace_time=30, # 错过执行的宽限时间(秒)
|
||||
coalesce=True # 合并错过的执行
|
||||
coalesce=True, # 合并错过的执行
|
||||
)
|
||||
|
||||
logger.info(f"任务已添加到调度器 [ID: {job_id}, cron: {job_cron}]")
|
||||
@@ -504,22 +498,24 @@ def run_job_once(job_id: int, params: Optional[Dict] = None) -> Dict:
|
||||
"""
|
||||
try:
|
||||
from src.modules.job.models import Job
|
||||
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
|
||||
if not job:
|
||||
return {'success': False, 'error': '任务不存在'}
|
||||
return {"success": False, "error": "任务不存在"}
|
||||
|
||||
# 直接执行任务(同步方式)
|
||||
return execute_job(job.id, job.url, 1, job.job_trigger, params or {})
|
||||
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': str(e)}
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 调度器初始化和重载函数
|
||||
# ============================================================
|
||||
|
||||
|
||||
def init_scheduler() -> int:
|
||||
"""
|
||||
初始化调度器
|
||||
@@ -538,10 +534,7 @@ def init_scheduler() -> int:
|
||||
logger.info("调度器已启动")
|
||||
|
||||
# 查询所有启用的任务
|
||||
jobs = Job.query.filter(
|
||||
Job.status == 1,
|
||||
Job.is_delete == 0
|
||||
).all()
|
||||
jobs = Job.query.filter(Job.status == 1, Job.is_delete == 0).all()
|
||||
|
||||
# 使用 model_to_dict 将每个 job 对象转换为字典
|
||||
# 避免 SQLAlchemy Session 分离问题
|
||||
|
||||
+42
-76
@@ -16,6 +16,7 @@ from src.modules.job.models import Job
|
||||
# 定时任务表单验证
|
||||
# ============================================================
|
||||
|
||||
|
||||
class JobForm(BaseForm):
|
||||
"""
|
||||
定时任务表单验证类
|
||||
@@ -25,112 +26,82 @@ class JobForm(BaseForm):
|
||||
|
||||
# 任务ID
|
||||
id = IntegerField(
|
||||
label='任务ID',
|
||||
validators=[
|
||||
Optional(),
|
||||
NumberRange(min=1, message='任务ID必须大于0')
|
||||
],
|
||||
description='任务唯一标识,更新时必填'
|
||||
label="任务ID",
|
||||
validators=[Optional(), NumberRange(min=1, message="任务ID必须大于0")],
|
||||
description="任务唯一标识,更新时必填",
|
||||
)
|
||||
|
||||
# 任务名称
|
||||
jobName = StringField(
|
||||
label='任务名称',
|
||||
validators=[
|
||||
DataRequired(message='任务名称不能为空'),
|
||||
Length(max=255, message='任务名称长度不得超过255个字符')
|
||||
],
|
||||
description='任务名称'
|
||||
label="任务名称",
|
||||
validators=[DataRequired(message="任务名称不能为空"), Length(max=255, message="任务名称长度不得超过255个字符")],
|
||||
description="任务名称",
|
||||
)
|
||||
|
||||
# 任务别名
|
||||
jobAlias = StringField(
|
||||
label='任务别名',
|
||||
validators=[
|
||||
Optional(),
|
||||
Length(max=255, message='任务别名长度不得超过255个字符')
|
||||
],
|
||||
description='任务别名'
|
||||
label="任务别名",
|
||||
validators=[Optional(), Length(max=255, message="任务别名长度不得超过255个字符")],
|
||||
description="任务别名",
|
||||
)
|
||||
|
||||
# 任务分组
|
||||
jobGroup = StringField(
|
||||
label='任务分组',
|
||||
validators=[
|
||||
Optional(),
|
||||
Length(max=255, message='任务分组长度不得超过255个字符')
|
||||
],
|
||||
description='任务分组,默认DEFAULT'
|
||||
label="任务分组",
|
||||
validators=[Optional(), Length(max=255, message="任务分组长度不得超过255个字符")],
|
||||
description="任务分组,默认DEFAULT",
|
||||
)
|
||||
|
||||
# 任务触发器
|
||||
jobTrigger = StringField(
|
||||
label='任务触发器',
|
||||
validators=[
|
||||
Optional(),
|
||||
Length(max=255, message='任务触发器长度不得超过255个字符')
|
||||
],
|
||||
description='任务触发器标识'
|
||||
label="任务触发器",
|
||||
validators=[Optional(), Length(max=255, message="任务触发器长度不得超过255个字符")],
|
||||
description="任务触发器标识",
|
||||
)
|
||||
|
||||
# 任务状态
|
||||
status = IntegerField(
|
||||
label='任务状态',
|
||||
validators=[
|
||||
DataRequired(message='任务状态不能为空'),
|
||||
NumberRange(min=0, max=3, message='任务状态值在0~3之间')
|
||||
],
|
||||
description='任务状态:0-未发布 1-运行中 2-暂停 3-删除'
|
||||
label="任务状态",
|
||||
validators=[DataRequired(message="任务状态不能为空"), NumberRange(min=0, max=3, message="任务状态值在0~3之间")],
|
||||
description="任务状态:0-未发布 1-运行中 2-暂停 3-删除",
|
||||
)
|
||||
|
||||
# Cron表达式
|
||||
cronExpression = StringField(
|
||||
label='Cron表达式',
|
||||
validators=[
|
||||
Optional(),
|
||||
Length(max=255, message='Cron表达式长度不得超过255个字符')
|
||||
],
|
||||
description='Cron表达式,如:0 2 * * *'
|
||||
label="Cron表达式",
|
||||
validators=[Optional(), Length(max=255, message="Cron表达式长度不得超过255个字符")],
|
||||
description="Cron表达式,如:0 2 * * *",
|
||||
)
|
||||
|
||||
# 执行策略
|
||||
executePolicy = IntegerField(
|
||||
label='执行策略',
|
||||
validators=[
|
||||
DataRequired(message='执行策略不能为空'),
|
||||
NumberRange(min=1, max=3, message='执行策略值在1~3之间')
|
||||
],
|
||||
description='执行策略:1-立即执行 2-执行一次 3-放弃执行'
|
||||
label="执行策略",
|
||||
validators=[DataRequired(message="执行策略不能为空"), NumberRange(min=1, max=3, message="执行策略值在1~3之间")],
|
||||
description="执行策略:1-立即执行 2-执行一次 3-放弃执行",
|
||||
)
|
||||
|
||||
# 是否同步任务
|
||||
isSync = IntegerField(
|
||||
label='是否同步任务',
|
||||
label="是否同步任务",
|
||||
validators=[
|
||||
DataRequired(message='是否同步任务不能为空'),
|
||||
NumberRange(min=0, max=1, message='是否同步任务值在0~1之间')
|
||||
DataRequired(message="是否同步任务不能为空"),
|
||||
NumberRange(min=0, max=1, message="是否同步任务值在0~1之间"),
|
||||
],
|
||||
description='是否同步任务:0-异步 1-同步'
|
||||
description="是否同步任务:0-异步 1-同步",
|
||||
)
|
||||
|
||||
# 任务URL
|
||||
url = StringField(
|
||||
label='任务URL',
|
||||
validators=[
|
||||
Optional(),
|
||||
Length(max=255, message='任务URL长度不得超过255个字符')
|
||||
],
|
||||
description='任务请求URL'
|
||||
label="任务URL",
|
||||
validators=[Optional(), Length(max=255, message="任务URL长度不得超过255个字符")],
|
||||
description="任务请求URL",
|
||||
)
|
||||
|
||||
# 任务备注
|
||||
note = StringField(
|
||||
label='任务备注',
|
||||
validators=[
|
||||
Optional(),
|
||||
Length(max=255, message='任务备注长度不得超过255个字符')
|
||||
],
|
||||
description='任务备注说明'
|
||||
label="任务备注",
|
||||
validators=[Optional(), Length(max=255, message="任务备注长度不得超过255个字符")],
|
||||
description="任务备注说明",
|
||||
)
|
||||
|
||||
def validate_job_name(self, field):
|
||||
@@ -152,23 +123,18 @@ class JobForm(BaseForm):
|
||||
# 任务状态设置表单
|
||||
# ============================================================
|
||||
|
||||
|
||||
class JobStatusForm(BaseForm):
|
||||
"""任务状态设置表单验证类"""
|
||||
|
||||
id = IntegerField(
|
||||
label='任务ID',
|
||||
validators=[
|
||||
DataRequired(message='任务ID不能为空'),
|
||||
NumberRange(min=1, message='任务ID必须大于0')
|
||||
],
|
||||
description='要修改状态的任务ID'
|
||||
label="任务ID",
|
||||
validators=[DataRequired(message="任务ID不能为空"), NumberRange(min=1, message="任务ID必须大于0")],
|
||||
description="要修改状态的任务ID",
|
||||
)
|
||||
|
||||
status = IntegerField(
|
||||
label='任务状态',
|
||||
validators=[
|
||||
Optional(),
|
||||
NumberRange(min=0, max=3, message='任务状态值在0~3之间')
|
||||
],
|
||||
description='目标状态:0-未发布 1-运行中 2-暂停 3-删除'
|
||||
label="任务状态",
|
||||
validators=[Optional(), NumberRange(min=0, max=3, message="任务状态值在0~3之间")],
|
||||
description="目标状态:0-未发布 1-运行中 2-暂停 3-删除",
|
||||
)
|
||||
|
||||
+121
-184
@@ -7,22 +7,21 @@
|
||||
|
||||
import logging
|
||||
|
||||
from flask import request
|
||||
|
||||
from src.common.constants.common import PAGE_SIZE
|
||||
from src.modules.job.schema import JobForm, JobStatusForm
|
||||
from src.modules.job.models import Job
|
||||
from src.core.exceptions import BizError
|
||||
from src.modules.job.scheduler_service import (
|
||||
add_job_to_scheduler,
|
||||
remove_job_from_scheduler,
|
||||
pause_job_in_scheduler,
|
||||
run_job_once as scheduler_run_job_once,
|
||||
reload_scheduler
|
||||
reload_scheduler,
|
||||
)
|
||||
from src.core import response as R
|
||||
from src.common.utils import regular
|
||||
from src.common.utils.model_utils import models_to_list, model_to_dict
|
||||
from src.common.utils.request import parse_request_body, parse_id_list, like_contains
|
||||
from src.common.utils.request import parse_id_list, like_contains
|
||||
from src.core.security import get_username
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -32,72 +31,60 @@ logger = logging.getLogger(__name__)
|
||||
# 任务分页查询
|
||||
# ============================================================
|
||||
|
||||
def get_job_page():
|
||||
|
||||
def get_job_page(params: dict) -> dict:
|
||||
"""查询定时任务分页数据"""
|
||||
try:
|
||||
page_no = int(request.args.get('pageNo', 1))
|
||||
page_size = int(request.args.get('pageSize', PAGE_SIZE))
|
||||
page_no = int(params.get("pageNo", 1))
|
||||
page_size = int(params.get("pageSize", PAGE_SIZE))
|
||||
|
||||
query = Job.query.filter(Job.is_delete == 0)
|
||||
query = Job.query.filter(Job.is_delete == 0)
|
||||
|
||||
# 筛选条件
|
||||
job_name = request.args.get('jobName')
|
||||
if job_name:
|
||||
query = query.filter(Job.job_name.like(like_contains(job_name), escape='\\'))
|
||||
# 筛选条件
|
||||
job_name = params.get("jobName")
|
||||
if job_name:
|
||||
query = query.filter(Job.job_name.like(like_contains(job_name), escape="\\"))
|
||||
|
||||
status = request.args.get('status')
|
||||
if status:
|
||||
query = query.filter(Job.status == status)
|
||||
status = params.get("status")
|
||||
if status:
|
||||
query = query.filter(Job.status == status)
|
||||
|
||||
job_group = request.args.get('jobGroup')
|
||||
if job_group:
|
||||
query = query.filter(Job.job_group == job_group)
|
||||
job_group = params.get("jobGroup")
|
||||
if job_group:
|
||||
query = query.filter(Job.job_group == job_group)
|
||||
|
||||
total = query.count()
|
||||
pages = (total + page_size - 1) // page_size if total > 0 else 0
|
||||
total = query.count()
|
||||
pages = (total + page_size - 1) // page_size if total > 0 else 0
|
||||
|
||||
job_list = query.order_by(Job.create_time.desc()) \
|
||||
.limit(page_size) \
|
||||
.offset((page_no - 1) * page_size) \
|
||||
.all()
|
||||
job_list = query.order_by(Job.create_time.desc()).limit(page_size).offset((page_no - 1) * page_size).all()
|
||||
|
||||
records = models_to_list(job_list)
|
||||
records = models_to_list(job_list)
|
||||
|
||||
# 添加状态文本
|
||||
for record in records:
|
||||
record['statusText'] = _get_status_text(record.get('status'))
|
||||
record['executePolicyText'] = _get_execute_policy_text(record.get('execute_policy'))
|
||||
# 添加状态文本
|
||||
for record in records:
|
||||
record["statusText"] = _get_status_text(record.get("status"))
|
||||
record["executePolicyText"] = _get_execute_policy_text(record.get("execute_policy"))
|
||||
|
||||
page_data = {
|
||||
'records': records,
|
||||
'total': total,
|
||||
'size': page_size,
|
||||
'current': page_no,
|
||||
'pages': pages
|
||||
}
|
||||
return R.ok(data=page_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"查询任务分页异常: {str(e)}")
|
||||
return R.failed(msg="查询失败")
|
||||
page_data = {"records": records, "total": total, "size": page_size, "current": page_no, "pages": pages}
|
||||
return page_data
|
||||
|
||||
|
||||
def _get_status_text(status):
|
||||
"""获取状态文本"""
|
||||
status_map = {0: '未发布', 1: '运行中', 2: '暂停', 3: '删除'}
|
||||
return status_map.get(status, '')
|
||||
status_map = {0: "未发布", 1: "运行中", 2: "暂停", 3: "删除"}
|
||||
return status_map.get(status, "")
|
||||
|
||||
|
||||
def _get_execute_policy_text(policy):
|
||||
"""获取执行策略文本"""
|
||||
policy_map = {1: '立即执行', 2: '执行一次', 3: '放弃执行'}
|
||||
return policy_map.get(policy, '')
|
||||
policy_map = {1: "立即执行", 2: "执行一次", 3: "放弃执行"}
|
||||
return policy_map.get(policy, "")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务详情查询
|
||||
# ============================================================
|
||||
|
||||
|
||||
def get_job_detail(job_id):
|
||||
"""查询定时任务详情"""
|
||||
try:
|
||||
@@ -114,74 +101,56 @@ def get_job_detail(job_id):
|
||||
# 任务添加
|
||||
# ============================================================
|
||||
|
||||
def add_job():
|
||||
|
||||
def add_job(data: dict) -> None:
|
||||
"""添加定时任务"""
|
||||
try:
|
||||
json_data, error = parse_request_body()
|
||||
if error:
|
||||
return R.failed(msg=error)
|
||||
|
||||
form = JobForm(data=json_data)
|
||||
if not form.validate():
|
||||
return R.failed(msg=regular.get_err(form))
|
||||
form = JobForm(data=data)
|
||||
if not form.validate():
|
||||
raise BizError(regular.get_err(form))
|
||||
|
||||
job = Job(**form.data)
|
||||
job.create_user = get_username()
|
||||
job.save()
|
||||
job = Job(**form.data)
|
||||
job.create_user = get_username()
|
||||
job.save()
|
||||
|
||||
# 添加到调度器
|
||||
if job.status == 1 and job.cron_expression:
|
||||
add_job_to_scheduler(job)
|
||||
|
||||
return R.ok(msg="添加成功")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"添加任务异常: {str(e)}")
|
||||
return R.failed(msg="添加任务失败,请稍后重试")
|
||||
# 添加到调度器
|
||||
if job.status == 1 and job.cron_expression:
|
||||
add_job_to_scheduler(job)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务更新
|
||||
# ============================================================
|
||||
|
||||
def update_job():
|
||||
|
||||
def update_job(data: dict) -> None:
|
||||
"""更新定时任务"""
|
||||
try:
|
||||
json_data, error = parse_request_body()
|
||||
if error:
|
||||
return R.failed(msg=error)
|
||||
|
||||
form = JobForm(data=json_data)
|
||||
if not form.validate():
|
||||
return R.failed(msg=regular.get_err(form))
|
||||
form = JobForm(data=data)
|
||||
if not form.validate():
|
||||
raise BizError(regular.get_err(form))
|
||||
|
||||
job_id = form.data.get('id')
|
||||
if not job_id:
|
||||
return R.failed("任务ID无效")
|
||||
job_id = form.data.get("id")
|
||||
if not job_id:
|
||||
raise BizError("任务ID无效")
|
||||
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
return R.failed("任务不存在")
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
raise BizError("任务不存在")
|
||||
|
||||
old_status = job.status
|
||||
old_cron = job.cron_expression
|
||||
old_status = job.status
|
||||
old_cron = job.cron_expression
|
||||
|
||||
# 更新字段
|
||||
for key, value in form.data.items():
|
||||
if key != 'id' and hasattr(job, key):
|
||||
setattr(job, key, value)
|
||||
# 更新字段
|
||||
for key, value in form.data.items():
|
||||
if key != "id" and hasattr(job, key):
|
||||
setattr(job, key, value)
|
||||
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
|
||||
# 处理调度器
|
||||
_handle_scheduler_update(job, old_status, old_cron)
|
||||
|
||||
return R.ok(msg="更新成功")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新任务异常: {str(e)}")
|
||||
return R.failed(msg="更新任务失败,请稍后重试")
|
||||
# 处理调度器
|
||||
_handle_scheduler_update(job, old_status, old_cron)
|
||||
|
||||
|
||||
def _handle_scheduler_update(job, old_status, old_cron):
|
||||
@@ -204,147 +173,115 @@ def _handle_scheduler_update(job, old_status, old_cron):
|
||||
# 任务删除
|
||||
# ============================================================
|
||||
|
||||
def delete_jobs(job_ids):
|
||||
|
||||
def delete_jobs(job_ids: str) -> int:
|
||||
"""删除定时任务(批量)"""
|
||||
if not job_ids:
|
||||
return R.failed("记录ID不存在")
|
||||
raise BizError("记录ID不存在")
|
||||
|
||||
id_list = parse_id_list(job_ids)
|
||||
if not id_list:
|
||||
return R.failed("无效的记录ID")
|
||||
raise BizError("无效的记录ID")
|
||||
|
||||
for job_id in id_list:
|
||||
remove_job_from_scheduler(job_id)
|
||||
|
||||
return Job.batch_delete_r(job_ids)
|
||||
job_ids_list = parse_id_list(job_ids)
|
||||
if not job_ids_list:
|
||||
raise BizError("无效的记录ID")
|
||||
return Job.batch_delete(job_ids_list)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 任务状态设置
|
||||
# ============================================================
|
||||
|
||||
def update_job_status():
|
||||
|
||||
def update_job_status(data: dict) -> None:
|
||||
"""设置任务状态"""
|
||||
try:
|
||||
json_data, error = parse_request_body()
|
||||
if error:
|
||||
return R.failed(msg=error)
|
||||
|
||||
form = JobStatusForm(data=json_data)
|
||||
if not form.validate():
|
||||
return R.failed(msg=regular.get_err(form))
|
||||
form = JobStatusForm(data=data)
|
||||
if not form.validate():
|
||||
raise BizError(regular.get_err(form))
|
||||
|
||||
job_id = int(form.data['id'])
|
||||
target_status = int(form.data['status'])
|
||||
job_id = int(form.data["id"])
|
||||
target_status = int(form.data["status"])
|
||||
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
return R.failed("任务不存在")
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
raise BizError("任务不存在")
|
||||
|
||||
old_status = job.status
|
||||
job.status = target_status
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
old_status = job.status
|
||||
job.status = target_status
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
|
||||
# 处理调度器
|
||||
if target_status == 1 and job.cron_expression:
|
||||
add_job_to_scheduler(job)
|
||||
elif target_status == 2:
|
||||
pause_job_in_scheduler(job_id)
|
||||
elif old_status == 1 and target_status != 1:
|
||||
remove_job_from_scheduler(job_id)
|
||||
|
||||
return R.ok(msg="设置成功")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"设置任务状态异常: {str(e)}")
|
||||
return R.failed(msg="设置任务状态失败,请稍后重试")
|
||||
# 处理调度器
|
||||
if target_status == 1 and job.cron_expression:
|
||||
add_job_to_scheduler(job)
|
||||
elif target_status == 2:
|
||||
pause_job_in_scheduler(job_id)
|
||||
elif old_status == 1 and target_status != 1:
|
||||
remove_job_from_scheduler(job_id)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 暂停任务
|
||||
# ============================================================
|
||||
|
||||
|
||||
def pause_job(job_id):
|
||||
"""暂停任务"""
|
||||
try:
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
return R.failed("任务不存在")
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
raise BizError("任务不存在")
|
||||
job.status = 2
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
|
||||
if job.status == 2:
|
||||
return R.ok(msg="任务已处于暂停状态")
|
||||
|
||||
job.status = 2
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
|
||||
pause_job_in_scheduler(job_id)
|
||||
|
||||
return R.ok(msg="暂停成功")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"暂停任务异常: {str(e)}")
|
||||
return R.failed(msg="暂停任务失败,请稍后重试")
|
||||
pause_job_in_scheduler(job_id)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 恢复任务
|
||||
# ============================================================
|
||||
|
||||
|
||||
def resume_job(job_id):
|
||||
"""恢复任务"""
|
||||
try:
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
return R.failed("任务不存在")
|
||||
job = Job.query.filter(Job.id == job_id, Job.is_delete == 0).first()
|
||||
if not job:
|
||||
raise BizError("任务不存在")
|
||||
if not job.cron_expression:
|
||||
raise BizError("Cron表达式为空,无法恢复")
|
||||
|
||||
if job.status == 1:
|
||||
return R.ok(msg="任务已处于运行中状态")
|
||||
job.status = 1
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
|
||||
if not job.cron_expression:
|
||||
return R.failed("Cron表达式为空,无法恢复")
|
||||
|
||||
job.status = 1
|
||||
job.update_user = get_username()
|
||||
job.save()
|
||||
|
||||
# 直接传入模型对象,与其它方法保持一致
|
||||
add_job_to_scheduler(job)
|
||||
|
||||
return R.ok(msg="恢复成功")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"恢复任务异常: {str(e)}")
|
||||
return R.failed(msg="恢复任务失败,请稍后重试")
|
||||
# 直接传入模型对象,与其它方法保持一致
|
||||
add_job_to_scheduler(job)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 立即执行
|
||||
# ============================================================
|
||||
|
||||
|
||||
def run_job_once(job_id):
|
||||
"""立即执行一次任务"""
|
||||
try:
|
||||
result = scheduler_run_job_once(job_id)
|
||||
if result.get('success'):
|
||||
return R.ok(data=result, msg="执行成功")
|
||||
else:
|
||||
return R.failed(msg=result.get('error', '执行失败'))
|
||||
except Exception as e:
|
||||
logger.error(f"立即执行异常: {str(e)}")
|
||||
return R.failed(msg="执行失败")
|
||||
result = scheduler_run_job_once(job_id)
|
||||
if not result.get("success"):
|
||||
raise BizError(result.get("error", "执行失败"))
|
||||
return result
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 重新加载调度器
|
||||
# ============================================================
|
||||
|
||||
|
||||
def reload_all_jobs():
|
||||
"""重新加载所有任务"""
|
||||
try:
|
||||
count = reload_scheduler()
|
||||
return R.ok(data=count, msg=f"已重新加载 {count} 个任务")
|
||||
except Exception as e:
|
||||
logger.error(f"重新加载异常: {str(e)}")
|
||||
return R.failed(msg="重新加载失败")
|
||||
count = reload_scheduler()
|
||||
return count
|
||||
|
||||
@@ -41,7 +41,6 @@ from config import (
|
||||
SMS_ALIYUN_ENDPOINT,
|
||||
SMS_ALIYUN_READ_TIMEOUT,
|
||||
SMS_ALIYUN_SIGN_NAME,
|
||||
SMS_PROVIDER,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -74,8 +73,7 @@ def _get_client():
|
||||
from alibabacloud_tea_openapi.models import Config
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"阿里云短信 SDK 未安装,请执行: "
|
||||
"pip install alibabacloud-dysmsapi20170525 alibabacloud-tea-openapi"
|
||||
"阿里云短信 SDK 未安装,请执行: " "pip install alibabacloud-dysmsapi20170525 alibabacloud-tea-openapi"
|
||||
)
|
||||
|
||||
config = Config(
|
||||
@@ -93,6 +91,7 @@ def _get_client():
|
||||
# 短信发送
|
||||
# ============================================================
|
||||
|
||||
|
||||
def send_sms(
|
||||
phone: Union[str, List[str]],
|
||||
template_code: str,
|
||||
@@ -135,7 +134,7 @@ def send_sms(
|
||||
return False
|
||||
|
||||
# 模板变量序列化
|
||||
params_str = json.dumps(template_params, ensure_ascii=False) if template_params else ''
|
||||
params_str = json.dumps(template_params, ensure_ascii=False) if template_params else ""
|
||||
|
||||
# 逐条发送(阿里云短信 API 不支持批量同发)
|
||||
all_success = True
|
||||
@@ -174,13 +173,12 @@ def _send_one(phone: str, sign_name: str, template_code: str, template_params: s
|
||||
response = client.send_sms(request)
|
||||
body = response.body
|
||||
|
||||
if body.code == 'OK':
|
||||
if body.code == "OK":
|
||||
logger.info(f"短信发送成功: phone={phone}, template={template_code}, biz_id={body.biz_id}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(
|
||||
f"短信发送失败: phone={phone}, template={template_code}, "
|
||||
f"code={body.code}, message={body.message}"
|
||||
f"短信发送失败: phone={phone}, template={template_code}, " f"code={body.code}, message={body.message}"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user