feat: implement database backup and restore functionality with authentication; add deployment script and service configuration
This commit is contained in:
105
app.py
105
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_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
33
deploy.sh
Normal 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
15
my-score.service
Normal 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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user