217 lines
6.6 KiB
Python
217 lines
6.6 KiB
Python
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/<filename>", methods=["GET"])
|
|
def download_file(filename):
|
|
try:
|
|
return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True)
|
|
except FileNotFoundError:
|
|
abort(404)
|
|
|
|
|
|
@app.route("/public/<path:filename>", 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", "<br>")
|
|
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/<project>", 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)
|