From 5721d26fae29ed79ccf10eb76913f83579221cab Mon Sep 17 00:00:00 2001 From: "ethan.chen" Date: Mon, 19 May 2025 17:55:39 +0800 Subject: [PATCH] feat: implement database backup and restore functionality with authentication; add deployment script and service configuration --- app.py | 105 +++++++++++++++++++++++++++++++++++++++++++++-- deploy.sh | 33 +++++++++++++++ my-score.service | 15 +++++++ requirements.txt | 2 +- 4 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 deploy.sh create mode 100644 my-score.service diff --git a/app.py b/app.py index 7284c4a..70afcaf 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,23 @@ -from flask import Flask, request, jsonify +from flask import Flask, request, jsonify, send_file from flask_sqlalchemy import SQLAlchemy -from flask_cors import CORS +from flask_httpauth import HTTPBasicAuth from datetime import datetime import os +import shutil +import json app = Flask(__name__) -CORS(app) +auth = HTTPBasicAuth() + +# 配置认证信息 +USERS = { + "admin": "your-secure-password" # 请修改为安全的密码 +} + +@auth.verify_password +def verify_password(username, password): + if username in USERS and USERS[username] == password: + return username # 配置数据库 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///media.db' @@ -29,8 +41,91 @@ class Media(db.Model): with app.app_context(): db.create_all() +# 添加数据库备份功能 +def backup_database(): + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + backup_dir = 'backups' + if not os.path.exists(backup_dir): + os.makedirs(backup_dir) + + backup_file = f'{backup_dir}/media_{timestamp}.db' + shutil.copy2('media.db', backup_file) + return backup_file + +@app.route('/api/backup', methods=['POST']) +@auth.login_required +def create_backup(): + try: + backup_file = backup_database() + return jsonify({ + "code": 0, + "data": { + "backup_file": backup_file, + "timestamp": datetime.now().isoformat() + }, + "message": "Backup created successfully" + }) + except Exception as e: + return jsonify({"code": 1, "data": {}, "message": str(e)}), 500 + +@app.route('/api/backup/list', methods=['GET']) +@auth.login_required +def list_backups(): + try: + backup_dir = 'backups' + if not os.path.exists(backup_dir): + return jsonify({ + "code": 0, + "data": {"backups": []}, + "message": "No backups found" + }) + + backups = [] + for file in os.listdir(backup_dir): + if file.endswith('.db'): + file_path = os.path.join(backup_dir, file) + backups.append({ + "filename": file, + "size": os.path.getsize(file_path), + "created_at": datetime.fromtimestamp(os.path.getctime(file_path)).isoformat() + }) + + return jsonify({ + "code": 0, + "data": {"backups": backups}, + "message": "Success" + }) + except Exception as e: + return jsonify({"code": 1, "data": {}, "message": str(e)}), 500 + +@app.route('/api/backup/restore/', methods=['POST']) +@auth.login_required +def restore_backup(filename): + try: + backup_file = os.path.join('backups', filename) + if not os.path.exists(backup_file): + return jsonify({"code": 1, "data": {}, "message": "Backup file not found"}), 404 + + # 先备份当前数据库 + current_backup = backup_database() + + # 恢复选定的备份 + shutil.copy2(backup_file, 'media.db') + + return jsonify({ + "code": 0, + "data": { + "restored_file": filename, + "current_backup": current_backup + }, + "message": "Backup restored successfully" + }) + except Exception as e: + return jsonify({"code": 1, "data": {}, "message": str(e)}), 500 + # API 路由 @app.route('/api/media/list', methods=['GET']) +@auth.login_required def get_all_media(): try: media_list = Media.query.all() @@ -54,6 +149,7 @@ def get_all_media(): return jsonify({"code": 1, "data": {}, "message": str(e)}), 500 @app.route('/api/media/create', methods=['POST']) +@auth.login_required def create_media(): try: data = request.json @@ -89,6 +185,7 @@ def create_media(): return jsonify({"code": 2, "data": {}, "message": str(e)}), 500 @app.route('/api/media/updateById/', methods=['PUT']) +@auth.login_required def update_media(media_id): try: media = Media.query.get_or_404(media_id) @@ -125,6 +222,7 @@ def update_media(media_id): return jsonify({"code": 2, "data": {}, "message": str(e)}), 500 @app.route('/api/media/deleteById/', methods=['DELETE']) +@auth.login_required def delete_media(media_id): try: media = Media.query.get_or_404(media_id) @@ -136,6 +234,7 @@ def delete_media(media_id): return jsonify({"code": 2, "data": {}, "message": str(e)}), 500 @app.route('/api/media/page', methods=['GET']) +@auth.login_required def get_media_page(): try: type = request.args.get('type') diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..9d918d9 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# 更新系统 +sudo apt update +sudo apt upgrade -y + +# 安装 Python 和依赖 +sudo apt install -y python3 python3-pip python3-venv + +# 创建项目目录 +mkdir -p /home/ubuntu/my-score/backend + +# 创建并激活虚拟环境 +python3 -m venv /home/ubuntu/my-score/backend/venv +source /home/ubuntu/my-score/backend/venv/bin/activate + +# 安装项目依赖 +pip install -r requirements.txt + +# 复制服务文件 +sudo cp my-score.service /etc/systemd/system/ + +# 重新加载 systemd +sudo systemctl daemon-reload + +# 启动服务 +sudo systemctl start my-score + +# 设置开机自启 +sudo systemctl enable my-score + +# 检查服务状态 +sudo systemctl status my-score \ No newline at end of file diff --git a/my-score.service b/my-score.service new file mode 100644 index 0000000..c22f6d9 --- /dev/null +++ b/my-score.service @@ -0,0 +1,15 @@ +[Unit] +Description=My Score Backend Service +After=network.target + +[Service] +User=ubuntu +Group=ubuntu +WorkingDirectory=/home/ubuntu/my-score/backend +Environment="PATH=/home/ubuntu/my-score/backend/venv/bin" +ExecStart=/home/ubuntu/my-score/backend/venv/bin/python app.py +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 28f7603..91a498d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Flask==3.0.2 Flask-SQLAlchemy==3.1.1 -Flask-Cors==4.0.0 +Flask-HTTPAuth==4.8.0 python-dotenv==1.0.1 SQLAlchemy==2.0.28 \ No newline at end of file