a1abe159fa
Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
94 lines
3.0 KiB
Python
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()
|