From 6129cd506dbb553f7949a20a951992bcbc47da6b Mon Sep 17 00:00:00 2001
From: superlishunqin <852326703@qq.com>
Date: Sun, 7 Jul 2024 01:03:28 +0800
Subject: [PATCH] 0707
---
.idea/vcs.xml | 6 +
app.py | 79 ++++++++---
app.txt | 223 ++++++++++++++++++++++++++++++
index.html | 264 +++++++++++++++++++-----------------
index.txt | 342 +++++++++++++++++++++++++++++++++++++++++++++++
submissions.csv | 7 +
submissions.xlsx | Bin 5218 -> 6394 bytes
7 files changed, 783 insertions(+), 138 deletions(-)
create mode 100644 .idea/vcs.xml
create mode 100644 app.txt
create mode 100644 index.txt
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app.py b/app.py
index d197057..19779eb 100644
--- a/app.py
+++ b/app.py
@@ -45,13 +45,13 @@ submissions_file = 'submissions.csv'
if not os.path.exists(submissions_file):
with open(submissions_file, 'w', newline='') as file:
writer = csv.writer(file)
- writer.writerow(['ID', '学生姓名', '学号', '提交的文件'])
+ writer.writerow(['ID', '学生姓名', '学号', '作业', '提交的文件'])
-def add_submission(student, student_id, filename):
+def add_submission(student, student_id, assignment, filename):
with open(submissions_file, 'a', newline='') as file:
writer = csv.writer(file)
- writer.writerow([datetime.datetime.now().isoformat(), student, student_id, filename])
+ writer.writerow([datetime.datetime.now().isoformat(), student, student_id, assignment, filename])
def generate_presigned_url(object_key, content_type, expiration=3600):
@@ -63,7 +63,7 @@ def generate_presigned_url(object_key, content_type, expiration=3600):
Params={
'Bucket': bucket_name,
'Key': object_key,
- 'ContentType': content_type # 使用实际文件的 Content-Type
+ 'ContentType': content_type
},
ExpiresIn=expiration,
HttpMethod='PUT'
@@ -81,27 +81,42 @@ def get_presigned_url():
student_id = request.args.get('student_id')
filename = request.args.get('filename')
content_type = request.args.get('content_type', 'application/octet-stream')
+ assignment = request.args.get('assignment')
logging.info(
- f"Received request for student: {student}, student_id: {student_id}, filename: {filename}, content_type: {content_type}")
+ f"Received request for student: {student}, student_id: {student_id}, filename: {filename}, content_type: {content_type}, assignment: {assignment}")
- if not student or not filename or not student_id:
- logging.warning("Missing student, student_id or filename parameter")
- return jsonify({'error': 'Student, student_id and filename parameters are required'}), 400
+ if not student or not filename or not student_id or not assignment:
+ logging.warning("Missing student, student_id, assignment or filename parameter")
+ return jsonify({'error': 'Student, student_id, assignment and filename parameters are required'}), 400
- folder_name = 'sure_homework_define_by_qin'
- object_key = f'{folder_name}/{student}-{filename}'
+ folder_name = f'sure_homework_define_by_qin/{assignment}'
+ new_filename = f'{student}_{student_id}_{filename}'
+ object_key = f'{folder_name}/{new_filename}'
- url = generate_presigned_url(object_key, content_type) # 包含 content_type
+ url = generate_presigned_url(object_key, content_type)
if not url:
logging.error("Failed to generate presigned URL")
return jsonify({'error': 'Failed to generate presigned URL'}), 500
- add_submission(student, student_id, filename)
-
- logging.info(f"Generated URL: {url}")
return jsonify({'url': url, 'content_type': content_type})
+@app.route('/record-submission', methods=['POST'])
+def record_submission():
+ data = request.json
+ student = data.get('student')
+ student_id = data.get('student_id')
+ assignment = data.get('assignment')
+ filename = data.get('filename')
+
+ if not student or not filename or not student_id or not assignment:
+ return jsonify({'error': 'Student, student_id, assignment and filename parameters are required'}), 400
+
+ add_submission(student, student_id, assignment, filename)
+
+ return jsonify({'status': 'success'})
+
+
@app.route('/')
def serve_index():
return send_from_directory('.', 'index.html')
@@ -139,10 +154,37 @@ def health_check():
@app.route('/download-submissions')
def download_submissions():
- df = pd.read_csv(submissions_file)
- output_file = 'submissions.xlsx'
- df.to_excel(output_file, index=False)
- return send_file(output_file, as_attachment=True)
+ try:
+ # 使用 csv 模块读取文件,这样可以处理不一致的行
+ with open(submissions_file, 'r', newline='') as file:
+ csv_reader = csv.reader(file)
+ headers = next(csv_reader) # 读取标题行
+ data = list(csv_reader) # 读取所有数据行
+
+ # 确定最大列数
+ max_columns = max(len(headers), max(len(row) for row in data))
+
+ # 创建一个新的数据列表,确保每行都有相同数量的列
+ normalized_data = []
+ for row in data:
+ normalized_data.append(row + [''] * (max_columns - len(row)))
+
+ # 创建 DataFrame
+ df = pd.DataFrame(normalized_data, columns=headers + [''] * (max_columns - len(headers)))
+
+ # 重命名列(如果需要)
+ expected_columns = ['ID', '学生姓名', '学号', '作业', '提交的文件']
+ df.columns = expected_columns + [f'额外列{i+1}' for i in range(len(df.columns) - len(expected_columns))]
+
+ # 删除完全为空的行和列
+ df = df.dropna(how='all', axis=0).dropna(how='all', axis=1)
+
+ output_file = 'submissions.xlsx'
+ df.to_excel(output_file, index=False)
+ return send_file(output_file, as_attachment=True)
+ except Exception as e:
+ logging.error(f"Error in download_submissions: {str(e)}", exc_info=True)
+ return jsonify({'error': str(e)}), 500
@app.before_request
@@ -179,4 +221,3 @@ if __name__ == '__main__':
# sys.exit(1)
app.run(debug=True)
-
diff --git a/app.txt b/app.txt
new file mode 100644
index 0000000..19779eb
--- /dev/null
+++ b/app.txt
@@ -0,0 +1,223 @@
+from flask import Flask, request, jsonify, send_from_directory, make_response, send_file
+from flask_cors import CORS
+import boto3
+from botocore.exceptions import NoCredentialsError, ClientError, EndpointConnectionError
+import os
+from dotenv import load_dotenv
+import logging
+import datetime
+import pytz
+from botocore.client import Config
+import csv
+import pandas as pd
+
+# 配置日志
+logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
+
+load_dotenv() # 从.env文件加载环境变量
+
+app = Flask(__name__, static_url_path='', static_folder='.')
+CORS(app, resources={r"/*": {"origins": "*", "methods": "GET,POST,PUT,DELETE,OPTIONS"}}) # 添加 CORS 支持
+
+aws_access_key_id = os.getenv('AWS_ACCESS_KEY_ID')
+aws_secret_access_key = os.getenv('AWS_SECRET_ACCESS_KEY')
+region_name = os.getenv('AWS_REGION')
+bucket_name = os.getenv('S3_BUCKET_NAME')
+
+# 打印环境变量 (仅用于调试,生产环境中请移除)
+print(f"AWS_ACCESS_KEY_ID: {aws_access_key_id}")
+print(f"AWS_SECRET_ACCESS_KEY: {'*' * len(aws_secret_access_key) if aws_secret_access_key else 'Not set'}")
+print(f"AWS_REGION: {region_name}")
+print(f"S3_BUCKET_NAME: {bucket_name}")
+
+s3_client = boto3.client(
+ 's3',
+ aws_access_key_id=aws_access_key_id,
+ aws_secret_access_key=aws_secret_access_key,
+ region_name=region_name,
+ config=Config(signature_version='s3v4') # 使用 S3v4 签名版本
+)
+
+# 跟踪学生提交信息
+submissions_file = 'submissions.csv'
+
+# 创建或者加载提交文件
+if not os.path.exists(submissions_file):
+ with open(submissions_file, 'w', newline='') as file:
+ writer = csv.writer(file)
+ writer.writerow(['ID', '学生姓名', '学号', '作业', '提交的文件'])
+
+
+def add_submission(student, student_id, assignment, filename):
+ with open(submissions_file, 'a', newline='') as file:
+ writer = csv.writer(file)
+ writer.writerow([datetime.datetime.now().isoformat(), student, student_id, assignment, filename])
+
+
+def generate_presigned_url(object_key, content_type, expiration=3600):
+ try:
+ current_time = datetime.datetime.now(pytz.UTC)
+ logging.info(f"Current UTC time before generating URL: {current_time}")
+
+ response = s3_client.generate_presigned_url('put_object',
+ Params={
+ 'Bucket': bucket_name,
+ 'Key': object_key,
+ 'ContentType': content_type
+ },
+ ExpiresIn=expiration,
+ HttpMethod='PUT'
+ )
+ logging.info(f"Generated presigned URL: {response}")
+ return response
+ except (NoCredentialsError, ClientError, EndpointConnectionError) as e:
+ logging.error(f"Error generating presigned URL: {str(e)}", exc_info=True)
+ return None
+
+
+@app.route('/generate-url', methods=['GET'])
+def get_presigned_url():
+ student = request.args.get('student')
+ student_id = request.args.get('student_id')
+ filename = request.args.get('filename')
+ content_type = request.args.get('content_type', 'application/octet-stream')
+ assignment = request.args.get('assignment')
+ logging.info(
+ f"Received request for student: {student}, student_id: {student_id}, filename: {filename}, content_type: {content_type}, assignment: {assignment}")
+
+ if not student or not filename or not student_id or not assignment:
+ logging.warning("Missing student, student_id, assignment or filename parameter")
+ return jsonify({'error': 'Student, student_id, assignment and filename parameters are required'}), 400
+
+ folder_name = f'sure_homework_define_by_qin/{assignment}'
+ new_filename = f'{student}_{student_id}_{filename}'
+ object_key = f'{folder_name}/{new_filename}'
+
+ url = generate_presigned_url(object_key, content_type)
+ if not url:
+ logging.error("Failed to generate presigned URL")
+ return jsonify({'error': 'Failed to generate presigned URL'}), 500
+
+ return jsonify({'url': url, 'content_type': content_type})
+
+
+@app.route('/record-submission', methods=['POST'])
+def record_submission():
+ data = request.json
+ student = data.get('student')
+ student_id = data.get('student_id')
+ assignment = data.get('assignment')
+ filename = data.get('filename')
+
+ if not student or not filename or not student_id or not assignment:
+ return jsonify({'error': 'Student, student_id, assignment and filename parameters are required'}), 400
+
+ add_submission(student, student_id, assignment, filename)
+
+ return jsonify({'status': 'success'})
+
+
+@app.route('/')
+def serve_index():
+ return send_from_directory('.', 'index.html')
+
+
+@app.route('/health')
+def health_check():
+ logging.info("Health check initiated")
+ try:
+ local_time = datetime.datetime.now()
+ utc_time = datetime.datetime.now(pytz.UTC)
+ logging.info(f"Local time: {local_time}, UTC time: {utc_time}")
+
+ logging.info("Attempting to list S3 buckets")
+ response = s3_client.list_buckets()
+ logging.info(f"Successfully listed buckets: {[bucket['Name'] for bucket in response['Buckets']]}")
+ return jsonify({
+ 'status': 'healthy',
+ 'message': 'AWS credentials are valid',
+ 'local_time': local_time.isoformat(),
+ 'utc_time': utc_time.isoformat()
+ }), 200
+ except NoCredentialsError:
+ logging.error("AWS credentials not found", exc_info=True)
+ return jsonify({'status': 'unhealthy', 'message': 'AWS credentials not found'}), 500
+ except ClientError as e:
+ error_code = e.response['Error']['Code']
+ error_message = e.response['Error']['Message']
+ logging.error(f"AWS client error: {error_code} - {error_message}", exc_info=True)
+ return jsonify({'status': 'unhealthy', 'message': f'AWS client error: {error_code} - {error_message}'}), 500
+ except Exception as e:
+ logging.error(f"Unexpected error during health check: {str(e)}", exc_info=True)
+ return jsonify({'status': 'unhealthy', 'message': f'Unexpected error: {str(e)}'}), 500
+
+
+@app.route('/download-submissions')
+def download_submissions():
+ try:
+ # 使用 csv 模块读取文件,这样可以处理不一致的行
+ with open(submissions_file, 'r', newline='') as file:
+ csv_reader = csv.reader(file)
+ headers = next(csv_reader) # 读取标题行
+ data = list(csv_reader) # 读取所有数据行
+
+ # 确定最大列数
+ max_columns = max(len(headers), max(len(row) for row in data))
+
+ # 创建一个新的数据列表,确保每行都有相同数量的列
+ normalized_data = []
+ for row in data:
+ normalized_data.append(row + [''] * (max_columns - len(row)))
+
+ # 创建 DataFrame
+ df = pd.DataFrame(normalized_data, columns=headers + [''] * (max_columns - len(headers)))
+
+ # 重命名列(如果需要)
+ expected_columns = ['ID', '学生姓名', '学号', '作业', '提交的文件']
+ df.columns = expected_columns + [f'额外列{i+1}' for i in range(len(df.columns) - len(expected_columns))]
+
+ # 删除完全为空的行和列
+ df = df.dropna(how='all', axis=0).dropna(how='all', axis=1)
+
+ output_file = 'submissions.xlsx'
+ df.to_excel(output_file, index=False)
+ return send_file(output_file, as_attachment=True)
+ except Exception as e:
+ logging.error(f"Error in download_submissions: {str(e)}", exc_info=True)
+ return jsonify({'error': str(e)}), 500
+
+
+@app.before_request
+def before_request_func():
+ if request.method == 'OPTIONS':
+ return _build_cors_preflight_response()
+
+
+def _build_cors_preflight_response():
+ response = make_response()
+ response.headers.add("Access-Control-Allow-Origin", "*")
+ response.headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+ response.headers.add("Access-Control-Allow-Headers", "Content-Type")
+ return response
+
+
+if __name__ == '__main__':
+ local_time = datetime.datetime.now()
+ utc_time = datetime.datetime.now(pytz.UTC)
+ logging.info(f"Application starting. Local time: {local_time}, UTC time: {utc_time}")
+
+ try:
+ logging.info("Validating AWS credentials on startup")
+ sts = boto3.client('sts',
+ aws_access_key_id=aws_access_key_id,
+ aws_secret_access_key=aws_secret_access_key,
+ region_name=region_name)
+ response = sts.get_caller_identity()
+ logging.info(f"AWS credentials validated successfully. Account ID: {response['Account']}")
+ except Exception as e:
+ logging.error(f"Failed to validate AWS credentials: {str(e)}", exc_info=True)
+ # 如果你想在凭证验证失败时退出程序,取消注释下面两行
+ # import sys
+ # sys.exit(1)
+
+ app.run(debug=True)
diff --git a/index.html b/index.html
index cf2e1a3..6d639cb 100644
--- a/index.html
+++ b/index.html
@@ -48,7 +48,7 @@
color: #555;
font-weight: 500;
}
- input[type="text"] {
+ input[type="text"], select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
@@ -80,14 +80,7 @@
border-color: #ccc;
}
.file-input-button input[type=file] {
- font-size: 100px;
- position: absolute;
- left: 0;
- top: 0;
- opacity: 0;
- cursor: pointer;
- width: 100%;
- height: 100%;
+ display: none;
}
#file-name {
margin-top: 5px;
@@ -167,13 +160,20 @@
+
+
+
+
@@ -195,122 +195,148 @@