refactor: optimize structure, stability and runtime performance
This commit is contained in:
@@ -2,7 +2,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import threading
|
||||
import time as time_mod
|
||||
import uuid
|
||||
|
||||
import database
|
||||
from flask import Blueprint, jsonify, request
|
||||
@@ -17,6 +21,13 @@ api_schedules_bp = Blueprint("api_schedules", __name__)
|
||||
_HHMM_RE = re.compile(r"^(\d{1,2}):(\d{2})$")
|
||||
|
||||
|
||||
def _request_json(default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
data = request.get_json(silent=True)
|
||||
return data if isinstance(data, dict) else default
|
||||
|
||||
|
||||
def _normalize_hhmm(value: object) -> str | None:
|
||||
match = _HHMM_RE.match(str(value or "").strip())
|
||||
if not match:
|
||||
@@ -28,18 +39,53 @@ def _normalize_hhmm(value: object) -> str | None:
|
||||
return f"{hour:02d}:{minute:02d}"
|
||||
|
||||
|
||||
def _normalize_random_delay(value) -> tuple[int | None, str | None]:
|
||||
try:
|
||||
normalized = int(value or 0)
|
||||
except Exception:
|
||||
return None, "random_delay必须是0或1"
|
||||
if normalized not in (0, 1):
|
||||
return None, "random_delay必须是0或1"
|
||||
return normalized, None
|
||||
|
||||
|
||||
def _parse_schedule_account_ids(raw_value) -> list:
|
||||
try:
|
||||
parsed = json.loads(raw_value or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return []
|
||||
return parsed if isinstance(parsed, list) else []
|
||||
|
||||
|
||||
def _get_owned_schedule_or_error(schedule_id: int):
|
||||
schedule = database.get_schedule_by_id(schedule_id)
|
||||
if not schedule:
|
||||
return None, (jsonify({"error": "定时任务不存在"}), 404)
|
||||
if schedule.get("user_id") != current_user.id:
|
||||
return None, (jsonify({"error": "无权访问"}), 403)
|
||||
return schedule, None
|
||||
|
||||
|
||||
def _ensure_user_accounts_loaded(user_id: int) -> None:
|
||||
if safe_get_user_accounts_snapshot(user_id):
|
||||
return
|
||||
load_user_accounts(user_id)
|
||||
|
||||
|
||||
def _parse_browse_type_or_error(raw_value, *, default=BROWSE_TYPE_SHOULD_READ):
|
||||
browse_type = validate_browse_type(raw_value, default=default)
|
||||
if not browse_type:
|
||||
return None, (jsonify({"error": "浏览类型无效"}), 400)
|
||||
return browse_type, None
|
||||
|
||||
|
||||
@api_schedules_bp.route("/api/schedules", methods=["GET"])
|
||||
@login_required
|
||||
def get_user_schedules_api():
|
||||
"""获取当前用户的所有定时任务"""
|
||||
schedules = database.get_user_schedules(current_user.id)
|
||||
import json
|
||||
|
||||
for s in schedules:
|
||||
try:
|
||||
s["account_ids"] = json.loads(s.get("account_ids", "[]") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
s["account_ids"] = []
|
||||
for schedule in schedules:
|
||||
schedule["account_ids"] = _parse_schedule_account_ids(schedule.get("account_ids"))
|
||||
return jsonify(schedules)
|
||||
|
||||
|
||||
@@ -47,23 +93,26 @@ def get_user_schedules_api():
|
||||
@login_required
|
||||
def create_user_schedule_api():
|
||||
"""创建用户定时任务"""
|
||||
data = request.json or {}
|
||||
data = _request_json()
|
||||
|
||||
name = data.get("name", "我的定时任务")
|
||||
schedule_time = data.get("schedule_time", "08:00")
|
||||
weekdays = data.get("weekdays", "1,2,3,4,5")
|
||||
browse_type = validate_browse_type(data.get("browse_type", BROWSE_TYPE_SHOULD_READ), default=BROWSE_TYPE_SHOULD_READ)
|
||||
if not browse_type:
|
||||
return jsonify({"error": "浏览类型无效"}), 400
|
||||
|
||||
browse_type, browse_error = _parse_browse_type_or_error(data.get("browse_type", BROWSE_TYPE_SHOULD_READ))
|
||||
if browse_error:
|
||||
return browse_error
|
||||
|
||||
enable_screenshot = data.get("enable_screenshot", 1)
|
||||
random_delay = int(data.get("random_delay", 0) or 0)
|
||||
random_delay, delay_error = _normalize_random_delay(data.get("random_delay", 0))
|
||||
if delay_error:
|
||||
return jsonify({"error": delay_error}), 400
|
||||
|
||||
account_ids = data.get("account_ids", [])
|
||||
|
||||
normalized_time = _normalize_hhmm(schedule_time)
|
||||
if not normalized_time:
|
||||
return jsonify({"error": "时间格式不正确,应为 HH:MM"}), 400
|
||||
if random_delay not in (0, 1):
|
||||
return jsonify({"error": "random_delay必须是0或1"}), 400
|
||||
|
||||
schedule_id = database.create_user_schedule(
|
||||
user_id=current_user.id,
|
||||
@@ -85,18 +134,11 @@ def create_user_schedule_api():
|
||||
@login_required
|
||||
def get_schedule_detail_api(schedule_id):
|
||||
"""获取定时任务详情"""
|
||||
schedule = database.get_schedule_by_id(schedule_id)
|
||||
if not schedule:
|
||||
return jsonify({"error": "定时任务不存在"}), 404
|
||||
if schedule["user_id"] != current_user.id:
|
||||
return jsonify({"error": "无权访问"}), 403
|
||||
schedule, error_response = _get_owned_schedule_or_error(schedule_id)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
schedule["account_ids"] = json.loads(schedule.get("account_ids", "[]") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
schedule["account_ids"] = []
|
||||
schedule["account_ids"] = _parse_schedule_account_ids(schedule.get("account_ids"))
|
||||
return jsonify(schedule)
|
||||
|
||||
|
||||
@@ -104,14 +146,12 @@ def get_schedule_detail_api(schedule_id):
|
||||
@login_required
|
||||
def update_schedule_api(schedule_id):
|
||||
"""更新定时任务"""
|
||||
schedule = database.get_schedule_by_id(schedule_id)
|
||||
if not schedule:
|
||||
return jsonify({"error": "定时任务不存在"}), 404
|
||||
if schedule["user_id"] != current_user.id:
|
||||
return jsonify({"error": "无权访问"}), 403
|
||||
_, error_response = _get_owned_schedule_or_error(schedule_id)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
data = request.json or {}
|
||||
allowed_fields = [
|
||||
data = _request_json()
|
||||
allowed_fields = {
|
||||
"name",
|
||||
"schedule_time",
|
||||
"weekdays",
|
||||
@@ -120,27 +160,26 @@ def update_schedule_api(schedule_id):
|
||||
"random_delay",
|
||||
"account_ids",
|
||||
"enabled",
|
||||
]
|
||||
|
||||
update_data = {k: v for k, v in data.items() if k in allowed_fields}
|
||||
}
|
||||
update_data = {key: value for key, value in data.items() if key in allowed_fields}
|
||||
|
||||
if "schedule_time" in update_data:
|
||||
normalized_time = _normalize_hhmm(update_data["schedule_time"])
|
||||
if not normalized_time:
|
||||
return jsonify({"error": "时间格式不正确,应为 HH:MM"}), 400
|
||||
update_data["schedule_time"] = normalized_time
|
||||
|
||||
if "random_delay" in update_data:
|
||||
try:
|
||||
update_data["random_delay"] = int(update_data.get("random_delay") or 0)
|
||||
except Exception:
|
||||
return jsonify({"error": "random_delay必须是0或1"}), 400
|
||||
if update_data["random_delay"] not in (0, 1):
|
||||
return jsonify({"error": "random_delay必须是0或1"}), 400
|
||||
random_delay, delay_error = _normalize_random_delay(update_data.get("random_delay"))
|
||||
if delay_error:
|
||||
return jsonify({"error": delay_error}), 400
|
||||
update_data["random_delay"] = random_delay
|
||||
|
||||
if "browse_type" in update_data:
|
||||
normalized = validate_browse_type(update_data.get("browse_type"), default=BROWSE_TYPE_SHOULD_READ)
|
||||
if not normalized:
|
||||
return jsonify({"error": "浏览类型无效"}), 400
|
||||
update_data["browse_type"] = normalized
|
||||
normalized_browse_type, browse_error = _parse_browse_type_or_error(update_data.get("browse_type"))
|
||||
if browse_error:
|
||||
return browse_error
|
||||
update_data["browse_type"] = normalized_browse_type
|
||||
|
||||
success = database.update_user_schedule(schedule_id, **update_data)
|
||||
if success:
|
||||
@@ -152,11 +191,9 @@ def update_schedule_api(schedule_id):
|
||||
@login_required
|
||||
def delete_schedule_api(schedule_id):
|
||||
"""删除定时任务"""
|
||||
schedule = database.get_schedule_by_id(schedule_id)
|
||||
if not schedule:
|
||||
return jsonify({"error": "定时任务不存在"}), 404
|
||||
if schedule["user_id"] != current_user.id:
|
||||
return jsonify({"error": "无权访问"}), 403
|
||||
_, error_response = _get_owned_schedule_or_error(schedule_id)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
success = database.delete_user_schedule(schedule_id)
|
||||
if success:
|
||||
@@ -168,13 +205,11 @@ def delete_schedule_api(schedule_id):
|
||||
@login_required
|
||||
def toggle_schedule_api(schedule_id):
|
||||
"""启用/禁用定时任务"""
|
||||
schedule = database.get_schedule_by_id(schedule_id)
|
||||
if not schedule:
|
||||
return jsonify({"error": "定时任务不存在"}), 404
|
||||
if schedule["user_id"] != current_user.id:
|
||||
return jsonify({"error": "无权访问"}), 403
|
||||
schedule, error_response = _get_owned_schedule_or_error(schedule_id)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
data = request.json
|
||||
data = _request_json()
|
||||
enabled = data.get("enabled", not schedule["enabled"])
|
||||
|
||||
success = database.toggle_user_schedule(schedule_id, enabled)
|
||||
@@ -187,22 +222,11 @@ def toggle_schedule_api(schedule_id):
|
||||
@login_required
|
||||
def run_schedule_now_api(schedule_id):
|
||||
"""立即执行定时任务"""
|
||||
import json
|
||||
import threading
|
||||
import time as time_mod
|
||||
import uuid
|
||||
|
||||
schedule = database.get_schedule_by_id(schedule_id)
|
||||
if not schedule:
|
||||
return jsonify({"error": "定时任务不存在"}), 404
|
||||
if schedule["user_id"] != current_user.id:
|
||||
return jsonify({"error": "无权访问"}), 403
|
||||
|
||||
try:
|
||||
account_ids = json.loads(schedule.get("account_ids", "[]") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
account_ids = []
|
||||
schedule, error_response = _get_owned_schedule_or_error(schedule_id)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
account_ids = _parse_schedule_account_ids(schedule.get("account_ids"))
|
||||
if not account_ids:
|
||||
return jsonify({"error": "没有配置账号"}), 400
|
||||
|
||||
@@ -210,8 +234,7 @@ def run_schedule_now_api(schedule_id):
|
||||
browse_type = normalize_browse_type(schedule.get("browse_type", BROWSE_TYPE_SHOULD_READ))
|
||||
enable_screenshot = schedule["enable_screenshot"]
|
||||
|
||||
if not safe_get_user_accounts_snapshot(user_id):
|
||||
load_user_accounts(user_id)
|
||||
_ensure_user_accounts_loaded(user_id)
|
||||
|
||||
from services.state import safe_create_batch, safe_finalize_batch_after_dispatch
|
||||
from services.task_batches import _send_batch_task_email_if_configured
|
||||
@@ -250,6 +273,7 @@ def run_schedule_now_api(schedule_id):
|
||||
if remaining["done"] or remaining["count"] > 0:
|
||||
return
|
||||
remaining["done"] = True
|
||||
|
||||
execution_duration = int(time_mod.time() - execution_start_time)
|
||||
database.update_schedule_execution_log(
|
||||
log_id,
|
||||
@@ -260,19 +284,17 @@ def run_schedule_now_api(schedule_id):
|
||||
status="completed",
|
||||
)
|
||||
|
||||
task_source = f"user_scheduled:{batch_id}"
|
||||
for account_id in account_ids:
|
||||
account = safe_get_account(user_id, account_id)
|
||||
if not account:
|
||||
skipped_count += 1
|
||||
continue
|
||||
if account.is_running:
|
||||
if (not account) or account.is_running:
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
task_source = f"user_scheduled:{batch_id}"
|
||||
with completion_lock:
|
||||
remaining["count"] += 1
|
||||
ok, msg = submit_account_task(
|
||||
|
||||
ok, _ = submit_account_task(
|
||||
user_id=user_id,
|
||||
account_id=account_id,
|
||||
browse_type=browse_type,
|
||||
|
||||
Reference in New Issue
Block a user