From 59e391d84af4510f19e6ce323a308036fa8e0532 Mon Sep 17 00:00:00 2001 From: LeonspaceX Date: Mon, 13 Oct 2025 21:47:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D/admin/get/backup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API文档.md | 12 +++++---- api_server.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/API文档.md b/API文档.md index a395355..de0719e 100644 --- a/API文档.md +++ b/API文档.md @@ -756,11 +756,13 @@ ``` [ - { - "filename": "230615_abcd1.png", - "url": "/img/230615_abcd1.png", - "upload_time": "2024-05-18 10:15:22" // 图片上传时间 - } + "/img/251012_nfXWz.png", + "/img/251012_JrTrD.png", + "/img/251012_sk7ll.png", + "/img/251012_8efux.png", + "/img/251012_zK8fz.jpg", + "/img/251012_B0nkO.jpg", + "/img/251012_nNV3o.png" ] ``` diff --git a/api_server.py b/api_server.py index 37c5507..f00e434 100644 --- a/api_server.py +++ b/api_server.py @@ -7,6 +7,9 @@ import zipfile from flask import send_file from werkzeug.utils import secure_filename import os +import shutil +import threading +import time # === Flask 初始化 === app = Flask(__name__) @@ -729,10 +732,68 @@ def admin_recover(): file.save(temp_path) try: - # 解压到当前目录,会覆盖数据库和 img 文件夹 + # 1) 解压到临时目录,不直接覆盖源目录 + extract_dir = os.path.join(BACKUP_FOLDER, f"extracted_{os.path.splitext(filename)[0]}") + if os.path.exists(extract_dir): + shutil.rmtree(extract_dir, ignore_errors=True) + os.makedirs(extract_dir, exist_ok=True) + with zipfile.ZipFile(temp_path, 'r') as zip_ref: - zip_ref.extractall(os.path.dirname(os.path.abspath(__file__))) - + zip_ref.extractall(extract_dir) + + # 2) 恢复 img 文件夹到应用目录 + src_img = os.path.join(extract_dir, 'img') + if os.path.isdir(src_img): + # 清空并复制 + shutil.rmtree(IMG_FOLDER, ignore_errors=True) + shutil.copytree(src_img, IMG_FOLDER) + + # 3) 恢复数据库到 DB_FILE + target_db = DB_FILE + db_basename = os.path.basename(target_db) + candidate = os.path.join(extract_dir, db_basename) + if not os.path.isfile(candidate): + # 兜底:在压缩包中搜索常见数据库文件扩展 + candidate = None + for root, _, files in os.walk(extract_dir): + for f in files: + if f.lower().endswith(('.db', '.sqlite', '.sqlite3')): + candidate = os.path.join(root, f) + break + if candidate: + break + if not candidate: + return jsonify({"status": "Fail", "reason": "DB file not found in backup"}), 400 + + # 处理 SQLite WAL/SHM,避免增量日志合并新数据 + wal = f"{target_db}-wal" + shm = f"{target_db}-shm" + for fpath in (wal, shm): + if os.path.exists(fpath): + try: + os.remove(fpath) + except Exception as e: + app.logger.warning(f"Failed to remove {fpath}: {e}") + + # 确保 instance 目录存在 + os.makedirs(os.path.dirname(target_db), exist_ok=True) + # 覆盖数据库文件 + shutil.copy2(candidate, target_db) + + # 4) 释放 SQLAlchemy 连接(如果持有旧句柄) + try: + db.session.remove() + db.engine.dispose() + except Exception: + pass + + # 5) 清理临时文件夹与压缩包 + try: + os.remove(temp_path) + except Exception: + pass + shutil.rmtree(extract_dir, ignore_errors=True) + return jsonify({"status": "OK"}), 200 except Exception as e: return jsonify({"status": "Fail", "reason": str(e)}), 500