from flask import Flask, request, send_from_directory, abort, jsonify from logging.handlers import RotatingFileHandler from sort_rule import sort_by_rule import csv import datetime import logging import os import re from config import PROJECT_DEADLINES app = Flask(__name__) # 上传文件的路径 UPLOAD_FOLDER = "./uploads" os.makedirs(UPLOAD_FOLDER, exist_ok=True) # 创建日志记录器 logger = logging.getLogger() logger.setLevel(logging.INFO) # 日志格式 formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") # 文件处理器 file_handler = RotatingFileHandler( "running.log", mode="a", maxBytes=1_000_000, backupCount=5 ) file_handler.setLevel(logging.WARNING) file_handler.setFormatter(formatter) # 控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) console_handler.setFormatter(formatter) # 日志记录器 logger.addHandler(file_handler) logger.addHandler(console_handler) # @app.route("/upload", methods=["POST"]) # def upload_file(): # if "file" not in request.files: # return jsonify({"error": "No file part"}), 400 # file = request.files["file"] # if file.filename == "": # return jsonify({"error": "No selected file"}), 400 # # 保存文件到指定目录 # file.save(os.path.join(UPLOAD_FOLDER, file.filename)) # return "File uploaded successfully" @app.route("/download/", methods=["GET"]) def download_file(filename): try: return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True) except FileNotFoundError: abort(404) @app.route("/public/", methods=["GET"]) def exp(filename): try: return send_from_directory("public", filename, as_attachment=True) except FileNotFoundError: abort(404) def get_csv_file_path(project): """ 根据项目名称返回 CSV 文件路径 """ return f"{project}_scores.csv" def write_to_csv(data, project): """ 将成绩记录写入 CSV 文件。根据项目名称选择CSV文件路径。 """ csv_file_path = get_csv_file_path(project) file_exists = os.path.isfile(csv_file_path) fieldnames = ["time", "id", "name", "score", "running_time", "details", "project"] existing_data = [] # 文件存在,先保存 if file_exists: with open(csv_file_path, mode="r", newline="", encoding="utf-8") as csvfile: reader = csv.DictReader(csvfile) fieldnames = reader.fieldnames # 获取原表头 existing_data = list(reader) # 读数据 # 扩展表头 for field in data.keys(): if field not in fieldnames: fieldnames.append(field) # 重写 with open(csv_file_path, mode="w", newline="", encoding="utf-8") as csvfile: writer = csv.DictWriter(csvfile, fieldnames=fieldnames) # 写表头 writer.writeheader() # 写回数据 if existing_data: writer.writerows(existing_data) # 写新数据 writer.writerow(data) def is_past_deadline(project): # 当前时间 current_time = datetime.datetime.now() # 截止时间 deadline_str = PROJECT_DEADLINES.get(project) if deadline_str: deadline = datetime.datetime.strptime(deadline_str, "%Y-%m-%d %H:%M:%S") return current_time > deadline return False @app.route("/upload_score", methods=["POST"]) def upload_score(): try: data = request.json logging.info(f"Received data") if ( "name" in data and "id" in data and "score" in data and "details" in data and "running_time" in data and "time" in data and "project" in data ): if not re.fullmatch(r"^\d{12}$", data["id"]): logging.warning(f"Invalid ID format: {data["id"]}, RAW data: {data}") return jsonify({"message": "学号不合规,不记录成绩!"}), 400 elif ( data["name"] == "test_name" or data["name"].startswith("test") or data["name"] == "name" or data["name"] == "" ): logging.warning(f"Invalid name: {data["name"]}, RAW data: {data}") return jsonify({"message": "姓名不合规,不记录成绩!"}), 400 # 去除姓名前后可能带有的空格 data["name"] = data["name"].strip() # 检查截止时间 if is_past_deadline(data["project"]): logging.warning( f"Past deadline for project: {data["project"]}, RAW data: {data}" ) return jsonify({"message": "已过成绩提交截止时间,上传成绩失败!"}), 403 data["details"] = data["details"].replace("\n", "
") write_to_csv(data, data["project"]) logging.info(f"Score uploaded successfully for ID: {data["id"]}") return jsonify({"message": "上传成绩成功!"}), 200 else: logging.error(f"Data format error: {data}") return jsonify({"message": "数据格式错误!"}), 400 except Exception as e: logging.error("Exception occurred: %s", str(e), exc_info=True) return jsonify({"message": f"错误 {str(e)}"}), 500 @app.route("/get_scores/", methods=["GET"]) def get_scores(project): try: # 获取排序字段 sort_by = request.args.get("sort_by", "time") limit = request.args.get("limit") # 获取 CSV 文件 csv_file_path = get_csv_file_path(project) if not os.path.exists(csv_file_path): return jsonify([]) with open(csv_file_path, mode="r", encoding="utf-8") as csvfile: reader = csv.DictReader(csvfile) # 预转换数据类型 data = [ { **row, "time": datetime.datetime.strptime( row["time"], "%Y-%m-%d %H:%M:%S" ), "score": float(row["score"]), "running_time": float(row["running_time"].replace("ms", "")), } for row in reader ] # 根据 sort_by 排序 scores = sort_by_rule(data, sort_by, limit) # 设置缓存和跨域头 response = jsonify(scores) response.headers.add("Access-Control-Allow-Origin", "*") response.headers.add("Cache-Control", "public, max-age=300") return response except Exception as e: return jsonify({"error": str(e)}), 500 if __name__ == "__main__": app.run(host="0.0.0.0", port=880, debug=True)