Files
crypto_monitor/lib/instance/journal_chart_async_lib.py
dekun 8f4ae8ad31 Speed up journal save with AJAX and async K-line charts.
Defer exchange chart generation to a background thread and save via XHR so fill-from-trade no longer blocks on full page reload.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-18 15:15:24 +08:00

94 lines
3.0 KiB
Python

"""复盘自动 K 线:后台线程生成,避免 /add_journal 同步卡住."""
from __future__ import annotations
import logging
import threading
from typing import Any, Callable
logger = logging.getLogger(__name__)
def schedule_journal_exchange_chart(
*,
entry_id: str,
exchange_symbol: str,
title_prefix: str,
journal_tfs: list[str],
journal_limit: int,
marker_payload: dict[str, Any],
upload_folder: str,
generate_chart_fn: Callable[..., str | None],
get_db_fn: Callable[[], Any],
) -> None:
"""提交后立即返回;线程内画图并写回 journal_entries.image."""
entry_id = str(entry_id or "").strip()
if not entry_id or not callable(generate_chart_fn) or not callable(get_db_fn):
return
tfs = [str(x).strip() for x in (journal_tfs or []) if str(x).strip()]
if not tfs:
return
def _run() -> None:
try:
chart_fname = f"journal_{entry_id}.png"
saved = generate_chart_fn(
exchange_symbol,
title_prefix,
timeframes=tfs,
limit=journal_limit,
out_dir=upload_folder,
filename=chart_fname,
filename_prefix="journal",
marker_payload=marker_payload,
marker_timeframes={x.lower() for x in tfs},
layout="vertical",
)
if not saved:
logger.warning("journal chart async empty entry_id=%s", entry_id)
return
conn = get_db_fn()
try:
conn.execute(
"UPDATE journal_entries SET image=? WHERE id=?",
(saved, entry_id),
)
conn.commit()
finally:
conn.close()
except Exception:
logger.exception("journal chart async failed entry_id=%s", entry_id)
threading.Thread(
target=_run,
name=f"journal-chart-{entry_id[:8]}",
daemon=True,
).start()
def request_wants_journal_ajax(request: Any) -> bool:
"""XHR / Accept:json / form ajax=1 → 返回 JSON,避免整页刷新."""
xrw = str(getattr(request, "headers", {}).get("X-Requested-With") or "").lower()
if xrw == "xmlhttprequest":
return True
form = getattr(request, "form", None)
if form is not None:
raw = str(form.get("ajax") or "").strip().lower()
if raw in ("1", "true", "yes", "on"):
return True
accept = str(getattr(request, "headers", {}).get("Accept") or "").lower()
if "application/json" in accept and accept.strip().startswith("application/json"):
return True
return False
def journal_ajax_or_flash_error(request: Any, msg: str, *, redirect_fn: Callable[[], Any]):
"""校验失败:AJAX 返回 JSON,否则 flash + 跳转."""
from flask import flash, jsonify
if request_wants_journal_ajax(request):
return jsonify({"ok": False, "msg": msg}), 400
flash(msg)
return redirect_fn()