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 @@ + + diff --git a/index.txt b/index.txt new file mode 100644 index 0000000..6d639cb --- /dev/null +++ b/index.txt @@ -0,0 +1,342 @@ + + + + + + 秀儿文件提交系统 + + + +
+
+ 描述性文本 +
+

秀儿文件提交系统

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
未选择文件
+
+ +
+
+
+
+
+
+
0%
+
+ 速度: 0 KB/s + 0 KB / 0 KB + 剩余时间: 计算中 +
+
+ 下载统计表格 +
+ + + + + + diff --git a/submissions.csv b/submissions.csv index e8c51f5..4c6ee6f 100644 --- a/submissions.csv +++ b/submissions.csv @@ -11,3 +11,10 @@ ID,学生姓名,学号,提交的文件 2024-07-03T02:43:01.033652,534534,543534,数学分析 陈纪修 第三版 上 by 陈纪修,于崇华,金路 (z-lib.org).pdf 2024-07-03T02:46:15.397526,CCC,AAA,"周易译注 (黄寿祺,张善文) (Z-Library).pdf" 2024-07-03T02:48:26.189052,2423,423423,阅读1.pdf +2024-07-03T03:06:53.527517,李顺,342,homework1,计算机网络_数据链路层.ppt +2024-07-03T03:10:31.977965,Sure,324234,homework2,Sure_324234_彩虹易支付纯净源码.zip +2024-07-03T03:15:33.951611,342,453534,homework2,342_453534_男人来自火星,女人来自金星(套装共4册) (约翰·格雷) (Z-Library).pdf +2024-07-03T03:22:12.013752,李无天,983,homework3,李无天_983_真正全集:王阳明全集(以权威的隆庆初刻本《王文成公全书》为底本,增补2卷旧本未刊内容!王阳明的“心学哲理、文治武功、传奇人生”全在这... (Z-Library).pdf +2024-07-03T03:24:24.223088,王刚,324,homework3,王刚_324_期末复习.pptx +2024-07-06T19:16:46.811930,嗷呜呜0706,321,homework1,嗷呜呜0706_321_知行合一王阳明套装 (度阴山) (Z-Library).pdf +2024-07-07T00:28:05.848994,test,432,homework1,期末复习.pptx diff --git a/submissions.xlsx b/submissions.xlsx index 1670d24..e3a2659 100644 Binary files a/submissions.xlsx and b/submissions.xlsx differ