feat: implement database backup and restore functionality with authentication; add deployment script and service configuration

This commit is contained in:
ethan.chen
2025-05-19 17:55:39 +08:00
parent ca22da355d
commit 5721d26fae
4 changed files with 151 additions and 4 deletions

105
app.py
View File

@@ -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_sqlalchemy import SQLAlchemy
from flask_cors import CORS from flask_httpauth import HTTPBasicAuth
from datetime import datetime from datetime import datetime
import os import os
import shutil
import json
app = Flask(__name__) 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' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///media.db'
@@ -29,8 +41,91 @@ class Media(db.Model):
with app.app_context(): with app.app_context():
db.create_all() 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/<filename>', 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 路由 # API 路由
@app.route('/api/media/list', methods=['GET']) @app.route('/api/media/list', methods=['GET'])
@auth.login_required
def get_all_media(): def get_all_media():
try: try:
media_list = Media.query.all() media_list = Media.query.all()
@@ -54,6 +149,7 @@ def get_all_media():
return jsonify({"code": 1, "data": {}, "message": str(e)}), 500 return jsonify({"code": 1, "data": {}, "message": str(e)}), 500
@app.route('/api/media/create', methods=['POST']) @app.route('/api/media/create', methods=['POST'])
@auth.login_required
def create_media(): def create_media():
try: try:
data = request.json data = request.json
@@ -89,6 +185,7 @@ def create_media():
return jsonify({"code": 2, "data": {}, "message": str(e)}), 500 return jsonify({"code": 2, "data": {}, "message": str(e)}), 500
@app.route('/api/media/updateById/<int:media_id>', methods=['PUT']) @app.route('/api/media/updateById/<int:media_id>', methods=['PUT'])
@auth.login_required
def update_media(media_id): def update_media(media_id):
try: try:
media = Media.query.get_or_404(media_id) 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 return jsonify({"code": 2, "data": {}, "message": str(e)}), 500
@app.route('/api/media/deleteById/<int:media_id>', methods=['DELETE']) @app.route('/api/media/deleteById/<int:media_id>', methods=['DELETE'])
@auth.login_required
def delete_media(media_id): def delete_media(media_id):
try: try:
media = Media.query.get_or_404(media_id) 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 return jsonify({"code": 2, "data": {}, "message": str(e)}), 500
@app.route('/api/media/page', methods=['GET']) @app.route('/api/media/page', methods=['GET'])
@auth.login_required
def get_media_page(): def get_media_page():
try: try:
type = request.args.get('type') type = request.args.get('type')

33
deploy.sh Normal file
View File

@@ -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

15
my-score.service Normal file
View File

@@ -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

View File

@@ -1,5 +1,5 @@
Flask==3.0.2 Flask==3.0.2
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
Flask-Cors==4.0.0 Flask-HTTPAuth==4.8.0
python-dotenv==1.0.1 python-dotenv==1.0.1
SQLAlchemy==2.0.28 SQLAlchemy==2.0.28