Add automatic database backup with download and restore docs.

Back up futures.db and uploads to /root/qihuo_backup on a daily schedule, expose backup downloads in settings, and document cross-server restore.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 13:04:48 +08:00
parent 508d85a282
commit 98239d29c1
7 changed files with 630 additions and 4 deletions
+74 -1
View File
@@ -51,6 +51,16 @@ 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
from db_conn import connect_db
from db_backup import (
backup_dir,
backup_in_progress,
default_restore_dir,
get_backup_last_at,
list_backups,
resolve_backup_file,
schedule_backup,
start_backup_worker,
)
from strategy.strategy_db import init_strategy_tables
from install_trading import install_trading
from vnpy_bridge import try_init_vnpy
@@ -404,6 +414,12 @@ def init_db():
set_setting("trailing_be_tick_buffer", "2")
if not get_setting("pending_order_timeout_min"):
set_setting("pending_order_timeout_min", "5")
if not get_setting("backup_auto_enabled"):
set_setting("backup_auto_enabled", "1")
if not get_setting("backup_auto_hour"):
set_setting("backup_auto_hour", "3")
if not get_setting("backup_keep_count"):
set_setting("backup_keep_count", "30")
if not get_setting("fee_source_mode"):
set_setting("fee_source_mode", "ctp")
set_setting("fee_source_mode", "ctp")
@@ -705,6 +721,7 @@ def start_background_threads():
daemon=True,
).start()
threading.Thread(target=refresh_main_index, daemon=True).start()
start_backup_worker(get_setting_fn=get_setting, set_setting_fn=set_setting)
# —————————————— 登录 ——————————————
@@ -1659,12 +1676,60 @@ def fees():
)
@app.route("/api/backup/list")
@login_required
def api_backup_list():
return jsonify(
{
"dir": str(backup_dir()),
"last_at": get_backup_last_at(get_setting),
"running": backup_in_progress(),
"items": list_backups(),
}
)
@app.route("/api/backup/download/<filename>")
@login_required
def api_backup_download(filename):
from flask import send_file
try:
path = resolve_backup_file(filename)
except (ValueError, FileNotFoundError) as exc:
return jsonify({"error": str(exc)}), 404
return send_file(path, as_attachment=True, download_name=path.name)
@app.route("/settings", methods=["GET", "POST"])
@login_required
def settings():
if request.method == "POST":
action = request.form.get("action")
if action == "wechat":
if action == "backup_now":
ok, msg = schedule_backup(
get_setting=get_setting,
set_setting=set_setting,
include_uploads=True,
)
flash(msg if ok else msg)
elif action == "backup_config":
auto = request.form.get("backup_auto_enabled") == "1"
set_setting("backup_auto_enabled", "1" if auto else "0")
try:
hour = int(request.form.get("backup_auto_hour", "3") or 3)
set_setting("backup_auto_hour", str(max(0, min(23, hour))))
except ValueError:
flash("自动备份小时无效")
return redirect(url_for("settings"))
try:
keep = int(request.form.get("backup_keep_count", "30") or 30)
set_setting("backup_keep_count", str(max(5, min(200, keep))))
except ValueError:
flash("保留份数无效")
return redirect(url_for("settings"))
flash("备份策略已保存")
elif action == "wechat":
webhook = request.form.get("wechat_webhook", "").strip()
set_setting("wechat_webhook", webhook)
flash("企业微信配置已保存")
@@ -1813,6 +1878,14 @@ def settings():
pending_order_timeout_min=get_setting("pending_order_timeout_min", "5"),
nav_items=get_nav_items(get_setting),
nav_toggles=NAV_TOGGLES,
backup_dir=str(backup_dir()),
backup_last_at=get_backup_last_at(get_setting),
backup_running=backup_in_progress(),
backup_items=list_backups(),
backup_auto_enabled=get_setting("backup_auto_enabled", "1") == "1",
backup_auto_hour=get_setting("backup_auto_hour", "3"),
backup_keep_count=get_setting("backup_keep_count", "30"),
backup_restore_dir=default_restore_dir(),
)