import os import uuid import logging from datetime import datetime from flask import Flask, current_app, session, redirect, url_for, request, flash, render_template, jsonify, send_from_directory from flask_sqlalchemy import SQLAlchemy from flask_mysqldb import MySQL import MySQLdb.cursors from werkzeug.utils import secure_filename from flask_login import LoginManager, UserMixin, login_user, login_required, current_user from wtforms import Form, StringField from wtforms.validators import Length, EqualTo, ValidationError import pymysql from cryptography.fernet import Fernet,InvalidToken # 生成密钥并保存到文件(仅在第一次运行时生成) def generate_key(): key = Fernet.generate_key() with open("secret.key", "wb") as key_file: key_file.write(key) # 加载密钥 def load_key(): return open("secret.key", "rb").read() # 加密函数 def encrypt_message(message): key = load_key() f = Fernet(key) encrypted_message = f.encrypt(message.encode()) return encrypted_message # 解密函数 def decrypt_message(encrypted_message): key = load_key() f = Fernet(key) try: print(f"Encrypted message: {encrypted_message}") decrypted_message = f.decrypt(encrypted_message).decode() return decrypted_message except (InvalidToken, ValueError) as e: logger.error(f"Failed to decrypt message: {e}") return None conn = pymysql.connect(host="localhost", port=3306, user="root", password="lin556688", database="mydatabase", charset="utf8mb4") # 初始化 Flask 应用 app = Flask(__name__) app.config['SECRET_KEY'] = 'your_secret_key' app.secret_key = os.urandom(24) # 生成更安全的会话密钥 app.config['UPLOAD_FOLDER'] = 'uploads/' # 设置上传文件存储目录 os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) # 确保目录存在 # 配置日志 logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # MySQL 配置 app.config['MYSQL_HOST'] = 'localhost' app.config['MYSQL_USER'] = 'root' # 替换为你的 MySQL 用户名 app.config['MYSQL_PASSWORD'] = 'lin556688' # 替换为你的 MySQL 密码 app.config['MYSQL_DB'] = 'mydatabase' app.config['MYSQL_CURSORCLASS'] = 'DictCursor' mysql = MySQL(app) ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} # 配置日志记录器 logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # 自定义日志处理器类 class DatabaseLogHandler(logging.Handler): def emit(self, record): log_entry = self.format(record) # 去掉时间戳中的毫秒部分 timestamp = datetime.strptime(record.asctime, '%Y-%m-%d %H:%M:%S,%f').strftime('%Y-%m-%d %H:%M:%S') cursor = mysql.connection.cursor() cursor.execute( "INSERT INTO logs (timestamp, level, message) VALUES (%s, %s, %s)", (timestamp, record.levelname, log_entry) ) mysql.connection.commit() cursor.close() # 添加自定义日志处理器 db_handler = DatabaseLogHandler() db_handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') db_handler.setFormatter(formatter) logger.addHandler(db_handler) def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS # 初始化 Flask-Login login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' class User(UserMixin): def __init__(self, id, username, role): self.id = id self.username = username self.role = role @login_manager.user_loader def load_user(user_id): cursor = mysql.connection.cursor() cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) user_data = cursor.fetchone() cursor.close() if user_data: return User(user_data['id'], user_data['username'], user_data['role']) return None @app.route('/') def home(): return render_template('login.html') @app.route('/view_logs') @login_required def view_logs(): with open('app.log', 'r') as log_file: logs = log_file.readlines() return render_template('logs.html', logs=logs) # 处理文件上传 @app.route('/upload', methods=['POST']) def upload_file(): logger.info("Handling file upload request") if 'file' not in request.files: logger.warning("No file part in request") return jsonify({'message': 'No file part'}), 400 file = request.files['file'] if file.filename == '': logger.warning("No selected file") return jsonify({'message': 'No selected file'}), 400 if file and allowed_file(file.filename): filename = secure_filename(f"{uuid.uuid4().hex}_{file.filename}") filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(filepath) logger.info(f"File uploaded successfully: {filename}") return jsonify({'message': 'File uploaded successfully', 'filepath': filepath}), 201 else: logger.warning("File type not allowed") return jsonify({'message': 'File type not allowed'}), 400 # 提供上传文件的访问 @app.route('/uploads/') def uploaded_file(filename): logger.info(f"Accessing uploaded file: {filename}") return send_from_directory(app.config['UPLOAD_FOLDER'], filename) # 登录页面 @app.route('/login', methods=['GET', 'POST']) def login(): logger.info("Handling login request") if request.method == 'POST': username = request.form['username'] password = request.form['password'] cursor = mysql.connection.cursor() cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password)) user_data = cursor.fetchone() cursor.close() if user_data: # 解密密码 decrypted_password = decrypt_message(user_data['password']) user = User(user_data['id'], user_data['username'], user_data['role']) login_user(user) logger.info(f"User {username} logged in with role {user_data['role']}") if user_data['role'] == '侦查者': return redirect(url_for('scout')) elif user_data['role'] == '指挥者': return redirect(url_for('commander')) elif user_data['role'] == '攻击者': return redirect(url_for('attacker')) else: logger.warning("Invalid credentials") return "Invalid credentials. Please try again." return render_template('login.html') # 处理数据库语句 def con_my_sql(sql_code): try: conn.ping(reconnect=True) cursor= conn.cursor(pymysql.cursors.DictCursor) cursor.execute(sql_code) conn.commit() conn.close() return cursor except pymysql.MySQLError as err_massage: conn.rollback() conn.close() return type(err_massage), err_massage class RegisterForm(Form): captcha = StringField(validators=[Length(min=4,max=4,message='校验码格式错误')]) username = StringField(validators=[Length(min=3,max=10,message='用户名长度必须在3到10个字符之间')]) password = StringField(validators=[Length(min=6,max=20,message='密码长度必须在6到20个字符之间')]) password_confirm = StringField(validators=[EqualTo('password',message='两次输入的密码不一致')]) def validate_captcha(self, filed): if filed.data not in ['1111', '2222', '3333']: raise ValidationError('校验码错误') # 注册页面 @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'GET': return render_template('register.html') else: form = RegisterForm(request.form) if form.validate(): username = form.username.data password = form.password.data captcha = form.captcha.data # 静态注册码进行角色注册 if captcha == "1111": role = "侦查者" if captcha == "2222": role = "指挥者" if captcha == "3333": role = "攻击者" code = "select * from users where username = '%s'" % username cursor_ans = con_my_sql(code) cursor_select = cursor_ans.fetchall() if len(cursor_select) > 0: return '用户已经存在 返回登录' else: # 加密密码 encrypted_password = encrypt_message(password) code = "INSERT INTO users(username, password, role) VALUES('%s', '%s', '%s')" % (username, encrypted_password, role) print(con_my_sql(code)) return '注册成功 返回登录' else: print(form.errors) return redirect(url_for('register')) # 侦查者页面 @app.route('/scout') @login_required def scout(): logger.info("Accessing scout page") if current_user.role != '侦查者': logger.warning("Unauthorized access to scout page") return '用户无此页面访问权限 返回登录' # 获取侦察者的通知 cursor = mysql.connection.cursor() cursor.execute("SELECT * FROM notifications WHERE user_id = %s ORDER BY created_at DESC", (current_user.id,)) notifications = cursor.fetchall() cursor.close() logger.info(f"Notifications fetched: {notifications}") # 解密通知 for notification in notifications: notification['message'] = decrypt_message(notification['message']) return render_template('scout.html', notifications=notifications) # 处理消息 @app.route('/handle_message//', methods=['POST']) @login_required def handle_message(message_id, action): logger.info(f"Handling message action: {action} for ID: {message_id}") if current_user.role != '指挥者': logger.warning("Unauthorized access to handle message") return redirect(url_for('login')) cursor = mysql.connection.cursor() if action == 'accept': cursor.execute("UPDATE messages SET status = 'accepted' WHERE id = %s", (message_id,)) flash('消息已接受') elif action == 'reject': cursor.execute("UPDATE messages SET status = 'rejected' WHERE id = %s", (message_id,)) flash('消息已打回') mysql.connection.commit() cursor.close() logger.info(f"Message status updated: {action}") # 获取消息内容 cursor = mysql.connection.cursor() cursor.execute("SELECT message FROM messages WHERE id = %s", (message_id,)) message_data = cursor.fetchone() cursor.close() logger.info(f"Message data fetched: {message_data}") # 解密消息 decrypted_message = decrypt_message(message_data['message']) # 将结果反馈给侦察者 cursor = mysql.connection.cursor() cursor.execute("SELECT id FROM users WHERE role = '侦查者'") scouts = cursor.fetchall() cursor.close() logger.info(f"Scouts found: {scouts}") for scout in scouts: cursor = mysql.connection.cursor() cursor.execute("INSERT INTO notifications (user_id, message_id, action, message) VALUES (%s, %s, %s, %s)", (scout['id'], message_id, action, message_data['message'])) mysql.connection.commit() cursor.close() logger.info(f"Notification inserted for scout: {scout['id']}") return redirect(url_for('commander')) # 清除通知 @app.route('/clear_notifications', methods=['POST']) @login_required def clear_notifications(): user_id = request.json.get('user_id') if not user_id: return jsonify({'success': False, 'message': 'User ID is required'}), 400 cursor = mysql.connection.cursor() cursor.execute("DELETE FROM notifications WHERE user_id = %s", (user_id,)) mysql.connection.commit() cursor.close() return jsonify({'success': True, 'message': 'Notifications cleared successfully'}) # 指挥者页面 @app.route('/commander') @login_required def commander(): logger.info("Accessing commander page") if current_user.role != '指挥者': logger.warning("Unauthorized access to commander page") return '用户无此页面访问权限 返回登录' # 获取特定目录下的所有文件和攻击坐标状态 directory = 'E:/_Ufo/0000jiegou/TheBattleCar/uploads' media_items = [] try: media_items = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and allowed_file(f)] except Exception as e: logger.error(f"Error accessing directory: {str(e)}") flash(f"Error accessing directory: {str(e)}") # 获取攻击状态 cursor = mysql.connection.cursor() cursor.execute("SELECT id, coordinate, attacked, created_at FROM attacks ORDER BY created_at DESC") attacks = cursor.fetchall() cursor.close() # 解密坐标 for attack in attacks: attack['coordinate'] = decrypt_message(attack['coordinate']) # 获取侦察者发送的消息 cursor = mysql.connection.cursor() cursor.execute("SELECT id, message, photo_url, created_at, status FROM messages ORDER BY created_at DESC") messages = cursor.fetchall() cursor.close() # 解密消息 for message in messages: message['message'] = decrypt_message(message['message']) return render_template('commander.html', media_items=media_items, attacks=attacks, messages=messages) # 指挥者发送攻击坐标 @app.route('/send_attack', methods=['POST']) @login_required def send_attack(): logger.info("Handling send attack request") if current_user.role != '指挥者': logger.warning("Unauthorized access to send attack") return redirect(url_for('login')) coordinate = request.form.get('coordinate') if not coordinate: logger.warning("No coordinate provided") return "No coordinate provided", 400 # 加密坐标 encrypted_coordinate = encrypt_message(coordinate) coordinate = encrypted_coordinate # 插入攻击坐标到数据库 cursor = mysql.connection.cursor() cursor.execute("INSERT INTO attacks (coordinate) VALUES (%s)", (coordinate,)) mysql.connection.commit() cursor.close() logger.info(f"Attack coordinate sent: {coordinate}") flash('攻击坐标已发送') return redirect(url_for('commander')) # 清除照片 @app.route('/clear_photos', methods=['POST']) @login_required def clear_photos(): logger.info("Handling clear photos request") if current_user.role != '指挥者': logger.warning("Unauthorized access to clear photos") return redirect(url_for('login')) directory = app.config['UPLOAD_FOLDER'] for filename in os.listdir(directory): file_path = os.path.join(directory, filename) try: if os.path.isfile(file_path): os.unlink(file_path) except Exception as e: logger.error(f"Error deleting file: {str(e)}") flash(f"Error deleting file: {str(e)}") logger.info("Photos cleared successfully") flash('照片已清空') return redirect(url_for('commander')) # 清除侦察者发送的消息 @app.route('/clear_messages', methods=['POST']) @login_required def clear_messages(): logger.info("Handling clear messages request") if current_user.role != '指挥者': logger.warning("Unauthorized access to clear messages") return redirect(url_for('login')) cursor = mysql.connection.cursor() cursor.execute("DELETE FROM messages") mysql.connection.commit() cursor.close() logger.info("Messages cleared successfully") flash('消息已清空') return redirect(url_for('commander')) # 攻击者页面 @app.route('/attacker') @login_required def attacker(): logger.info("Accessing attacker page") if current_user.role != '攻击者': logger.warning("Unauthorized access to attacker page") return '用户无此页面访问权限 返回登录' # 获取攻击坐标列表 cursor = mysql.connection.cursor() cursor.execute("SELECT id, coordinate, created_at FROM attacks ORDER BY created_at DESC") attacks = cursor.fetchall() cursor.close() # 解密坐标 for attack in attacks: attack['coordinate'] = decrypt_message(attack['coordinate']) return render_template('attacker.html', attacks=attacks) # 攻击者执行攻击 @app.route('/execute_attack/', methods=['POST']) @login_required def execute_attack(attack_id): logger.info(f"Handling execute attack request for ID: {attack_id}") if current_user.role != '攻击者': logger.warning("Unauthorized access to execute attack") return redirect(url_for('login')) cursor = mysql.connection.cursor() cursor.execute("UPDATE attacks SET attacked = TRUE WHERE id = %s", (attack_id,)) mysql.connection.commit() cursor.close() logger.info(f"Attack executed for ID: {attack_id}") flash(f'已对坐标ID为 {attack_id} 的目标完成打击') return redirect(url_for('attacker')) # 退出登录(清除会话) @app.route('/logout') @login_required def logout(): logger.info("Handling logout request") session.pop('username', None) session.pop('role', None) return redirect(url_for('login')) # 发送消息 @app.route('/send_message', methods=['GET', 'POST']) @login_required def send_message(): logger.info("Handling send message request") if request.method == 'POST': message = request.form.get('message') if not message: logger.warning("No message provided") return "No message provided", 400 photo_url = None if 'photo' in request.files: file = request.files['photo'] if file.filename != '': if file and allowed_file(file.filename): filename = secure_filename(f"{uuid.uuid4().hex}_{file.filename}") filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(filepath) photo_url = url_for('uploaded_file', filename=filename, _external=True) else: logger.warning("Allowed file types are png, jpg, jpeg, gif") return "Allowed file types are png, jpg, jpeg, gif", 400 # 加密数据 encrypted_message = encrypt_message(message) message = encrypted_message # 插入数据到 MySQL cursor = mysql.connection.cursor() cursor.execute("INSERT INTO messages (message, photo_url, status) VALUES (%s, %s, %s)", (message, photo_url, 'pending')) mysql.connection.commit() cursor.close() logger.info(f"Message sent: {message}, Photo URL: {photo_url}") return f"Message and photo (if uploaded) have been received. Message: {message}\nPhoto URL: {photo_url if photo_url else 'N/A'}" return render_template('send_message.html') @app.route('/messages', methods=['GET']) @login_required def messages(): logger.info("Handling messages request") # 角色检查 if current_user.role != '指挥者': logger.warning("Unauthorized access to messages") if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json': return jsonify({"error": "Unauthorized access"}), 403 else: return redirect(url_for('login')) # 获取消息 cursor = mysql.connection.cursor() cursor.execute("SELECT id, message, photo_url, created_at, status FROM messages ORDER BY created_at DESC") result = cursor.fetchall() cursor.close() # 解密消息 messages = [] for row in result: decrypted_message = decrypt_message(row['message']) messages.append({ 'id': row['id'], 'message': decrypted_message, 'photo_url': row['photo_url'], 'created_at': row['created_at'], 'status': row['status'] }) # 根据请求类型返回不同的响应 if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json': return jsonify(messages) else: return render_template('view_messages.html', messages=messages) if __name__ == '__main__': generate_key() # 仅在第一次运行时生成密钥 app.run(debug=True, host='0.0.0.0', port=8000)