|
|
|
@ -8,12 +8,17 @@ import uuid
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from sqlalchemy.exc import OperationalError
|
|
|
|
|
import logging
|
|
|
|
|
from functools import wraps
|
|
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__, static_folder='../frontend/dist')
|
|
|
|
|
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev_key_for_goldminer')
|
|
|
|
|
# 设置会话cookie的设置
|
|
|
|
|
app.config['SESSION_COOKIE_SECURE'] = False # 开发环境设为False,生产环境设为True
|
|
|
|
|
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
|
|
|
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
|
|
|
|
|
|
|
|
|
# 云端MySQL数据库配置
|
|
|
|
|
DB_USER = os.environ.get('DB_USER', 'goldminer')
|
|
|
|
@ -29,7 +34,13 @@ app.config['SQLALCHEMY_ECHO'] = True # 开启SQL查询日志,方便调试
|
|
|
|
|
|
|
|
|
|
logger.info(f"连接到云端数据库: {DB_HOST}:{DB_PORT}/{DB_NAME}")
|
|
|
|
|
|
|
|
|
|
CORS(app, supports_credentials=True, origins=['*']) # Enable CORS with credentials for all origins
|
|
|
|
|
# 确保CORS配置正确处理凭证
|
|
|
|
|
CORS(app,
|
|
|
|
|
supports_credentials=True,
|
|
|
|
|
origins=['*'],
|
|
|
|
|
methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
|
|
|
allow_headers=['Content-Type', 'Authorization', 'X-Requested-With']) # 允许所有源的凭证请求
|
|
|
|
|
|
|
|
|
|
db = SQLAlchemy(app)
|
|
|
|
|
socketio = SocketIO(app, cors_allowed_origins="*", engineio_logger=True)
|
|
|
|
|
|
|
|
|
@ -37,6 +48,9 @@ socketio = SocketIO(app, cors_allowed_origins="*", engineio_logger=True)
|
|
|
|
|
active_games = {} # 用户ID -> 游戏状态
|
|
|
|
|
sid_to_user = {} # Socket ID -> 用户ID映射
|
|
|
|
|
|
|
|
|
|
# 添加一个简单的令牌存储
|
|
|
|
|
user_tokens = {} # token -> user_id 映射
|
|
|
|
|
|
|
|
|
|
# 用户模型
|
|
|
|
|
class User(db.Model):
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
@ -45,6 +59,7 @@ class User(db.Model):
|
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
|
last_login = db.Column(db.DateTime, nullable=True)
|
|
|
|
|
high_score = db.Column(db.Integer, default=0)
|
|
|
|
|
is_admin = db.Column(db.Boolean, default=False)
|
|
|
|
|
|
|
|
|
|
def set_password(self, password):
|
|
|
|
|
self.password_hash = generate_password_hash(password)
|
|
|
|
@ -96,6 +111,9 @@ def init_db():
|
|
|
|
|
# 尝试创建表结构(如果不存在)
|
|
|
|
|
db.create_all()
|
|
|
|
|
logger.info("数据库连接成功,并且所有表都已创建(如果不存在)。")
|
|
|
|
|
|
|
|
|
|
# 检查是否需要创建默认管理员账户
|
|
|
|
|
create_default_admin()
|
|
|
|
|
except OperationalError as e:
|
|
|
|
|
logger.error("无法连接到云端MySQL数据库。请检查您的数据库配置和网络连接。")
|
|
|
|
|
logger.error(f"错误详情: {e}")
|
|
|
|
@ -107,6 +125,31 @@ def init_db():
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"初始化数据库时发生未知错误: {e}")
|
|
|
|
|
|
|
|
|
|
# 创建默认管理员账户
|
|
|
|
|
def create_default_admin():
|
|
|
|
|
# 检查是否已有管理员账户
|
|
|
|
|
admin_exists = User.query.filter_by(is_admin=True).first()
|
|
|
|
|
if admin_exists:
|
|
|
|
|
logger.info("已存在管理员账户,无需创建默认管理员。")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 检查是否已有admin用户名的账户
|
|
|
|
|
admin_user = User.query.filter_by(username="admin").first()
|
|
|
|
|
|
|
|
|
|
if admin_user:
|
|
|
|
|
# 将现有的admin用户升级为管理员
|
|
|
|
|
admin_user.is_admin = True
|
|
|
|
|
db.session.commit()
|
|
|
|
|
logger.info("已将现有admin用户升级为管理员。")
|
|
|
|
|
else:
|
|
|
|
|
# 创建新的默认管理员账户
|
|
|
|
|
default_admin = User(username="admin", is_admin=True)
|
|
|
|
|
default_admin.set_password("admin")
|
|
|
|
|
|
|
|
|
|
db.session.add(default_admin)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
logger.info("已创建默认管理员账户(用户名:admin,密码:admin)。在生产环境中请更改此密码!")
|
|
|
|
|
|
|
|
|
|
# 注册API
|
|
|
|
|
@app.route('/api/register', methods=['POST'])
|
|
|
|
|
def register():
|
|
|
|
@ -133,9 +176,11 @@ def register():
|
|
|
|
|
@app.route('/api/login', methods=['POST'])
|
|
|
|
|
def login():
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
logger.info(f"收到登录请求: {data.get('username') if data else 'No data'}")
|
|
|
|
|
|
|
|
|
|
# 检查必要的字段
|
|
|
|
|
if not data or not data.get('username') or not data.get('password'):
|
|
|
|
|
logger.warning("登录请求缺少用户名或密码")
|
|
|
|
|
return jsonify({'error': '用户名和密码是必填项'}), 400
|
|
|
|
|
|
|
|
|
|
# 查找用户
|
|
|
|
@ -143,31 +188,56 @@ def login():
|
|
|
|
|
|
|
|
|
|
# 检查密码
|
|
|
|
|
if user and user.check_password(data['password']):
|
|
|
|
|
# 生成会话ID
|
|
|
|
|
# 生成会话ID和令牌
|
|
|
|
|
session_id = str(uuid.uuid4())
|
|
|
|
|
token = str(uuid.uuid4())
|
|
|
|
|
|
|
|
|
|
# 存储到会话和令牌映射
|
|
|
|
|
session['user_id'] = user.id
|
|
|
|
|
session['session_id'] = session_id
|
|
|
|
|
user_tokens[token] = user.id
|
|
|
|
|
|
|
|
|
|
# 更新最后登录时间
|
|
|
|
|
user.last_login = datetime.utcnow()
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user.id}) 登录成功,设置会话ID: {session_id} 和令牌: {token[:8]}...")
|
|
|
|
|
logger.info(f"当前会话内容: {dict(session)}")
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'message': '登录成功',
|
|
|
|
|
'user': {
|
|
|
|
|
'id': user.id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'high_score': user.high_score
|
|
|
|
|
'high_score': user.high_score,
|
|
|
|
|
'is_admin': user.is_admin
|
|
|
|
|
},
|
|
|
|
|
'session_id': session_id
|
|
|
|
|
'session_id': session_id,
|
|
|
|
|
'token': token
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
logger.warning(f"用户名或密码不正确: {data.get('username')}")
|
|
|
|
|
return jsonify({'error': '用户名或密码不正确'}), 401
|
|
|
|
|
|
|
|
|
|
# 登出API
|
|
|
|
|
@app.route('/api/logout', methods=['POST'])
|
|
|
|
|
def logout():
|
|
|
|
|
# 获取并清除令牌
|
|
|
|
|
if 'Authorization' in request.headers:
|
|
|
|
|
token = request.headers.get('Authorization').replace('Bearer ', '')
|
|
|
|
|
if token in user_tokens:
|
|
|
|
|
logger.info(f"清除令牌: {token[:8]}...")
|
|
|
|
|
del user_tokens[token]
|
|
|
|
|
|
|
|
|
|
# 获取用户ID用于日志记录
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if user_id:
|
|
|
|
|
logger.info(f"用户 ID: {user_id} 登出")
|
|
|
|
|
|
|
|
|
|
# 清除会话
|
|
|
|
|
session.clear()
|
|
|
|
|
logger.info("会话已清除")
|
|
|
|
|
|
|
|
|
|
return jsonify({'message': '已成功登出'})
|
|
|
|
|
|
|
|
|
|
# 获取当前用户信息
|
|
|
|
@ -186,7 +256,8 @@ def get_user():
|
|
|
|
|
'user': {
|
|
|
|
|
'id': user.id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'high_score': user.high_score
|
|
|
|
|
'high_score': user.high_score,
|
|
|
|
|
'is_admin': user.is_admin
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
@ -573,6 +644,277 @@ def send_active_games_update():
|
|
|
|
|
# 广播活跃游戏状态
|
|
|
|
|
socketio.emit('active_games_update', {'active_games': active_games})
|
|
|
|
|
|
|
|
|
|
# 管理员权限验证装饰器
|
|
|
|
|
def admin_required(f):
|
|
|
|
|
@wraps(f)
|
|
|
|
|
def decorated_function(*args, **kwargs):
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return jsonify({'error': '未登录'}), 401
|
|
|
|
|
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user or not user.is_admin:
|
|
|
|
|
return jsonify({'error': '需要管理员权限'}), 403
|
|
|
|
|
|
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
return decorated_function
|
|
|
|
|
|
|
|
|
|
# 获取所有用户信息 (管理员功能)
|
|
|
|
|
@app.route('/api/admin/users', methods=['GET'])
|
|
|
|
|
@admin_required
|
|
|
|
|
def get_all_users():
|
|
|
|
|
users = User.query.all()
|
|
|
|
|
users_data = []
|
|
|
|
|
|
|
|
|
|
for user in users:
|
|
|
|
|
users_data.append({
|
|
|
|
|
'id': user.id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'high_score': user.high_score,
|
|
|
|
|
'is_admin': user.is_admin,
|
|
|
|
|
'created_at': user.created_at.isoformat() if user.created_at else None,
|
|
|
|
|
'last_login': user.last_login.isoformat() if user.last_login else None
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return jsonify({'users': users_data})
|
|
|
|
|
|
|
|
|
|
# 编辑用户信息 (管理员功能)
|
|
|
|
|
@app.route('/api/admin/users/<int:user_id>', methods=['PUT'])
|
|
|
|
|
@admin_required
|
|
|
|
|
def update_user(user_id):
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return jsonify({'error': '用户不存在'}), 404
|
|
|
|
|
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
|
|
|
|
|
# 只允许更新特定字段
|
|
|
|
|
if 'username' in data and data['username'] != user.username:
|
|
|
|
|
# 检查用户名是否已存在
|
|
|
|
|
if User.query.filter_by(username=data['username']).first():
|
|
|
|
|
return jsonify({'error': '用户名已存在'}), 400
|
|
|
|
|
user.username = data['username']
|
|
|
|
|
|
|
|
|
|
if 'high_score' in data:
|
|
|
|
|
user.high_score = data['high_score']
|
|
|
|
|
|
|
|
|
|
if 'is_admin' in data:
|
|
|
|
|
# 确保至少有一个管理员账户
|
|
|
|
|
if not data['is_admin'] and user.is_admin:
|
|
|
|
|
admin_count = User.query.filter_by(is_admin=True).count()
|
|
|
|
|
if admin_count <= 1:
|
|
|
|
|
return jsonify({'error': '系统必须至少有一个管理员账户'}), 400
|
|
|
|
|
user.is_admin = data['is_admin']
|
|
|
|
|
|
|
|
|
|
if 'password' in data and data['password']:
|
|
|
|
|
user.set_password(data['password'])
|
|
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
# 如果修改了排行榜相关信息,发送更新事件
|
|
|
|
|
if 'high_score' in data:
|
|
|
|
|
send_leaderboard_update()
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'message': '用户信息已更新',
|
|
|
|
|
'user': {
|
|
|
|
|
'id': user.id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'high_score': user.high_score,
|
|
|
|
|
'is_admin': user.is_admin,
|
|
|
|
|
'created_at': user.created_at.isoformat() if user.created_at else None,
|
|
|
|
|
'last_login': user.last_login.isoformat() if user.last_login else None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# 删除用户 (管理员功能)
|
|
|
|
|
@app.route('/api/admin/users/<int:user_id>', methods=['DELETE'])
|
|
|
|
|
@admin_required
|
|
|
|
|
def delete_user(user_id):
|
|
|
|
|
# 获取当前管理员
|
|
|
|
|
admin_id = session.get('user_id')
|
|
|
|
|
admin = User.query.get(admin_id)
|
|
|
|
|
|
|
|
|
|
# 防止删除自己
|
|
|
|
|
if user_id == admin_id:
|
|
|
|
|
return jsonify({'error': '不能删除自己的账户'}), 400
|
|
|
|
|
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return jsonify({'error': '用户不存在'}), 404
|
|
|
|
|
|
|
|
|
|
# 删除用户的所有游戏记录
|
|
|
|
|
GameHistory.query.filter_by(user_id=user_id).delete()
|
|
|
|
|
|
|
|
|
|
# 删除用户的所有聊天消息
|
|
|
|
|
ChatMessage.query.filter_by(user_id=user_id).delete()
|
|
|
|
|
|
|
|
|
|
# 删除用户创建的聊天室
|
|
|
|
|
ChatRoom.query.filter_by(creator_id=user_id).delete()
|
|
|
|
|
|
|
|
|
|
# 删除用户
|
|
|
|
|
db.session.delete(user)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
# 更新排行榜
|
|
|
|
|
send_leaderboard_update()
|
|
|
|
|
|
|
|
|
|
return jsonify({'message': '用户已成功删除'})
|
|
|
|
|
|
|
|
|
|
# 清理排行榜记录 (管理员功能)
|
|
|
|
|
@app.route('/api/admin/leaderboard/reset', methods=['POST'])
|
|
|
|
|
@admin_required
|
|
|
|
|
def reset_leaderboard():
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
|
|
|
|
|
# 如果指定了具体用户,只重置该用户的分数
|
|
|
|
|
if data and 'user_id' in data:
|
|
|
|
|
user = User.query.get(data['user_id'])
|
|
|
|
|
if not user:
|
|
|
|
|
return jsonify({'error': '用户不存在'}), 404
|
|
|
|
|
|
|
|
|
|
user.high_score = 0
|
|
|
|
|
GameHistory.query.filter_by(user_id=data['user_id']).delete()
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
send_leaderboard_update()
|
|
|
|
|
return jsonify({'message': f'已重置用户 {user.username} 的分数'})
|
|
|
|
|
|
|
|
|
|
# 重置所有用户的分数
|
|
|
|
|
users = User.query.all()
|
|
|
|
|
for user in users:
|
|
|
|
|
user.high_score = 0
|
|
|
|
|
|
|
|
|
|
# 删除所有游戏历史记录
|
|
|
|
|
GameHistory.query.delete()
|
|
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
send_leaderboard_update()
|
|
|
|
|
return jsonify({'message': '所有排行榜记录已重置'})
|
|
|
|
|
|
|
|
|
|
# 修改游戏历史记录 (管理员功能)
|
|
|
|
|
@app.route('/api/admin/game_history', methods=['GET'])
|
|
|
|
|
@admin_required
|
|
|
|
|
def get_all_game_history():
|
|
|
|
|
# 获取所有游戏历史记录,最多返回100条
|
|
|
|
|
history = GameHistory.query.order_by(GameHistory.created_at.desc()).limit(100).all()
|
|
|
|
|
|
|
|
|
|
history_data = []
|
|
|
|
|
for record in history:
|
|
|
|
|
user = User.query.get(record.user_id)
|
|
|
|
|
history_data.append({
|
|
|
|
|
'id': record.id,
|
|
|
|
|
'username': user.username if user else 'Unknown',
|
|
|
|
|
'user_id': record.user_id,
|
|
|
|
|
'score': record.score,
|
|
|
|
|
'level_reached': record.level_reached,
|
|
|
|
|
'duration': record.duration,
|
|
|
|
|
'gold_earned': record.gold_earned,
|
|
|
|
|
'created_at': record.created_at.isoformat()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return jsonify({'history': history_data})
|
|
|
|
|
|
|
|
|
|
# 删除指定的游戏记录 (管理员功能)
|
|
|
|
|
@app.route('/api/admin/game_history/<int:history_id>', methods=['DELETE'])
|
|
|
|
|
@admin_required
|
|
|
|
|
def delete_game_history(history_id):
|
|
|
|
|
record = GameHistory.query.get(history_id)
|
|
|
|
|
if not record:
|
|
|
|
|
return jsonify({'error': '记录不存在'}), 404
|
|
|
|
|
|
|
|
|
|
db.session.delete(record)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
return jsonify({'message': '记录已成功删除'})
|
|
|
|
|
|
|
|
|
|
# 设置管理员 (用于初始化第一个管理员账户)
|
|
|
|
|
@app.route('/api/admin/setup', methods=['POST'])
|
|
|
|
|
def setup_admin():
|
|
|
|
|
# 检查是否已有管理员账户
|
|
|
|
|
admin_exists = User.query.filter_by(is_admin=True).first()
|
|
|
|
|
if admin_exists:
|
|
|
|
|
return jsonify({'error': '管理员账户已存在,不能再创建'}), 400
|
|
|
|
|
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
|
|
|
|
|
# 检查必要的字段
|
|
|
|
|
if not data or not data.get('username') or not data.get('password') or not data.get('setup_key'):
|
|
|
|
|
return jsonify({'error': '用户名、密码和安装密钥是必填项'}), 400
|
|
|
|
|
|
|
|
|
|
# 验证安装密钥 (这应该是在环境变量中配置的一个安全密钥)
|
|
|
|
|
setup_key = os.environ.get('ADMIN_SETUP_KEY', 'goldminer_admin_setup_key')
|
|
|
|
|
if data['setup_key'] != setup_key:
|
|
|
|
|
return jsonify({'error': '安装密钥不正确'}), 403
|
|
|
|
|
|
|
|
|
|
# 检查用户名是否已存在
|
|
|
|
|
existing_user = User.query.filter_by(username=data['username']).first()
|
|
|
|
|
|
|
|
|
|
if existing_user:
|
|
|
|
|
# 如果用户存在,将其升级为管理员
|
|
|
|
|
existing_user.is_admin = True
|
|
|
|
|
db.session.commit()
|
|
|
|
|
return jsonify({'message': '用户已成功升级为管理员', 'username': existing_user.username}), 200
|
|
|
|
|
else:
|
|
|
|
|
# 创建新管理员用户
|
|
|
|
|
admin = User(username=data['username'], is_admin=True)
|
|
|
|
|
admin.set_password(data['password'])
|
|
|
|
|
|
|
|
|
|
db.session.add(admin)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
return jsonify({'message': '管理员账户创建成功', 'username': admin.username}), 201
|
|
|
|
|
|
|
|
|
|
# 测试管理员API
|
|
|
|
|
@app.route('/api/test/admin_status', methods=['GET'])
|
|
|
|
|
def test_admin_status():
|
|
|
|
|
# 尝试从会话获取用户ID
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
|
|
|
|
|
# 如果会话中没有用户ID,尝试从令牌获取
|
|
|
|
|
if not user_id and 'Authorization' in request.headers:
|
|
|
|
|
token = request.headers.get('Authorization').replace('Bearer ', '')
|
|
|
|
|
user_id = user_tokens.get(token)
|
|
|
|
|
logger.info(f"从令牌获取用户ID: {user_id}")
|
|
|
|
|
|
|
|
|
|
logger.info(f"收到管理员状态检查请求,会话内容: {dict(session)}")
|
|
|
|
|
logger.info(f"请求头: {request.headers}")
|
|
|
|
|
|
|
|
|
|
if not user_id:
|
|
|
|
|
logger.warning("会话和令牌中都没有用户ID,未登录状态")
|
|
|
|
|
return jsonify({
|
|
|
|
|
'logged_in': False,
|
|
|
|
|
'message': '未登录',
|
|
|
|
|
'session_data': dict(session),
|
|
|
|
|
'headers': dict(request.headers)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
logger.warning(f"用户ID {user_id} 不存在")
|
|
|
|
|
return jsonify({
|
|
|
|
|
'logged_in': False,
|
|
|
|
|
'message': '用户不存在',
|
|
|
|
|
'user_id': user_id
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
logger.info(f"管理员状态检查:用户 {user.username} (ID: {user.id}) 的管理员状态为 {user.is_admin}")
|
|
|
|
|
return jsonify({
|
|
|
|
|
'logged_in': True,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'is_admin': user.is_admin,
|
|
|
|
|
'is_admin_type': type(user.is_admin).__name__,
|
|
|
|
|
'user_data': {
|
|
|
|
|
'id': user.id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'high_score': user.high_score,
|
|
|
|
|
'is_admin': user.is_admin,
|
|
|
|
|
'created_at': user.created_at.isoformat() if user.created_at else None,
|
|
|
|
|
'last_login': user.last_login.isoformat() if user.last_login else None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
# 在启动应用前初始化数据库
|
|
|
|
|
init_db()
|
|
|
|
|