K线后台自动刷新并通过SSE推送到前端,移除轮询
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from werkzeug.utils import secure_filename
|
||||
from dotenv import load_dotenv
|
||||
from flask import (
|
||||
Flask, render_template, request, redirect, url_for,
|
||||
flash, session, jsonify,
|
||||
flash, session, jsonify, Response, stream_with_context,
|
||||
)
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
@@ -31,6 +31,7 @@ from fee_sync import sync_fees_from_akshare
|
||||
from contract_profile import get_contract_profile
|
||||
from stats_engine import STATS_VIEWS, load_stats_cache, refresh_stats_cache
|
||||
from kline_store import ensure_kline_tables
|
||||
from kline_stream import kline_hub, sse_format
|
||||
from kline_chart import generate_review_kline_chart, fetch_market_klines, MARKET_PERIODS
|
||||
from market import get_price as market_get_price, set_ths_refresh_token, get_quote_source_label
|
||||
|
||||
@@ -364,6 +365,29 @@ def sync_ths_token():
|
||||
|
||||
sync_ths_token()
|
||||
|
||||
|
||||
def build_market_quote_payload(
|
||||
symbol: str,
|
||||
market_code: str = "",
|
||||
sina_code: str = "",
|
||||
) -> dict:
|
||||
if not market_code or not sina_code:
|
||||
codes = ths_to_codes(symbol)
|
||||
if codes:
|
||||
market_code = codes.get("market_code", "") or market_code
|
||||
sina_code = codes.get("sina_code", "") or sina_code
|
||||
price = market_get_price(market_code, sina_code)
|
||||
name = symbol
|
||||
codes = ths_to_codes(symbol)
|
||||
if codes:
|
||||
name = codes.get("name", symbol)
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"name": name,
|
||||
"price": price,
|
||||
}
|
||||
|
||||
|
||||
# —————————————— 推送 ——————————————
|
||||
|
||||
def send_wechat_msg(content: str):
|
||||
@@ -542,6 +566,17 @@ def background_task():
|
||||
pass
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def start_background_threads():
|
||||
threading.Thread(target=background_task, daemon=True).start()
|
||||
threading.Thread(
|
||||
target=lambda: kline_hub.worker_loop(DB_PATH, build_market_quote_payload),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
|
||||
start_background_threads()
|
||||
|
||||
# —————————————— 登录 ——————————————
|
||||
|
||||
def login_required(f):
|
||||
@@ -1334,6 +1369,48 @@ def api_kline():
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route("/api/kline/stream")
|
||||
@login_required
|
||||
def api_kline_stream():
|
||||
from queue import Empty
|
||||
|
||||
symbol = request.args.get("symbol", "").strip()
|
||||
period = request.args.get("period", "15m").strip()
|
||||
market_code = request.args.get("market_code", "").strip()
|
||||
sina_code = request.args.get("sina_code", "").strip()
|
||||
if not symbol:
|
||||
return jsonify({"error": "请提供合约代码"}), 400
|
||||
|
||||
def generate():
|
||||
sub = kline_hub.subscribe(symbol, period, market_code, sina_code)
|
||||
try:
|
||||
kline_data = fetch_market_klines(symbol, period, DB_PATH)
|
||||
if kline_data.get("bars"):
|
||||
yield sse_format("kline", kline_data)
|
||||
yield sse_format(
|
||||
"quote",
|
||||
build_market_quote_payload(symbol, market_code, sina_code),
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
msg = sub.queue.get(timeout=20)
|
||||
yield sse_format(msg["event"], msg["data"])
|
||||
except Empty:
|
||||
yield ": heartbeat\n\n"
|
||||
finally:
|
||||
kline_hub.unsubscribe(sub)
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/market_quote")
|
||||
@login_required
|
||||
def api_market_quote():
|
||||
@@ -1342,21 +1419,7 @@ def api_market_quote():
|
||||
sina_code = request.args.get("sina_code", "").strip()
|
||||
if not symbol and not market_code:
|
||||
return jsonify({"error": "请提供合约"}), 400
|
||||
if not market_code or not sina_code:
|
||||
codes = ths_to_codes(symbol)
|
||||
if codes:
|
||||
market_code = codes.get("market_code", "") or market_code
|
||||
sina_code = codes.get("sina_code", "") or sina_code
|
||||
price = market_get_price(market_code, sina_code)
|
||||
name = symbol
|
||||
codes = ths_to_codes(symbol)
|
||||
if codes:
|
||||
name = codes.get("name", symbol)
|
||||
return jsonify({
|
||||
"symbol": symbol,
|
||||
"name": name,
|
||||
"price": price,
|
||||
})
|
||||
return jsonify(build_market_quote_payload(symbol, market_code, sina_code))
|
||||
|
||||
|
||||
@app.route("/contract")
|
||||
@@ -1491,5 +1554,4 @@ def settings():
|
||||
# —————————————— 启动 ——————————————
|
||||
|
||||
if __name__ == "__main__":
|
||||
threading.Thread(target=background_task, daemon=True).start()
|
||||
app.run(host=HOST, port=PORT, debug=DEBUG)
|
||||
|
||||
Reference in New Issue
Block a user