diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ae5d85d Binary files /dev/null and b/.DS_Store differ diff --git a/.env b/.env index 6b614fa..92796f5 100644 --- a/.env +++ b/.env @@ -1,4 +1,24 @@ +SECRET_KEY=6c9d28c778888d3fc7c459155e6593f8 + +# 邮箱配置 +EMAIL_HOST=mail.sq0715.com +EMAIL_PORT=465 +MAIL_USE_SSL=True +EMAIL_USERNAME=vip@sq0715.com +EMAIL_PASSWORD=Lsq12350501. +EMAIL_FROM_NAME=Qin +EMAIL_FROM=vip@sq0715.com + +# AWS 配置 AWS_ACCESS_KEY_ID=AKIAZQ3DT3KLI6N5LQUM AWS_SECRET_ACCESS_KEY=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 AWS_REGION=ap-northeast-1 -S3_BUCKET_NAME=sure-ae-upload +S3_BUCKET_NAME=qin-test-file-system + +# MySQL 配置 +MYSQL_HOST=8.218.165.242 +MYSQL_USER=sure_001 +MYSQL_PASSWORD=EKKWLMmrGmG7sdPf +MYSQL_DB=sure_001 + + diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..aa15e3e --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +app.py \ No newline at end of file diff --git a/.idea/AWS-sure.iml b/.idea/AWS-sure.iml index 0ed9eec..efb8c9e 100644 --- a/.idea/AWS-sure.iml +++ b/.idea/AWS-sure.iml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index ec6a681..a971a2c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/__pycache__/app_function.cpython-311.pyc b/__pycache__/app_function.cpython-311.pyc new file mode 100644 index 0000000..03e1fb2 Binary files /dev/null and b/__pycache__/app_function.cpython-311.pyc differ diff --git a/app.py b/app.py index 19779eb..bda5c19 100644 --- a/app.py +++ b/app.py @@ -1,223 +1,50 @@ -from flask import Flask, request, jsonify, send_from_directory, make_response, send_file +from flask import Flask, request, jsonify, redirect, url_for, render_template, session, make_response from flask_cors import CORS -import boto3 -from botocore.exceptions import NoCredentialsError, ClientError, EndpointConnectionError -import os +from flask_mail import Mail +from flask_bcrypt import Bcrypt +from flask_session import Session from dotenv import load_dotenv +import os import logging -import datetime -import pytz -from botocore.client import Config -import csv -import pandas as pd +import app_function # 引入新的文件 + +# 载入环境变量 +load_dotenv() + +# 初始化 Flask 应用 +app = Flask(__name__, static_url_path='', static_folder='.') +CORS(app, resources={r"/*": {"origins": "*", "methods": "GET,POST,PUT,DELETE,OPTIONS"}}) +bcrypt = Bcrypt(app) + +# 确保 SECRET_KEY 被设置 +app.secret_key = os.getenv('SECRET_KEY', 'you_will_never_guess') +app.config['SESSION_TYPE'] = 'filesystem' +app.config['SESSION_PERMANENT'] = False +Session(app) + + + +# 配置邮件 +app.config.update( + MAIL_SERVER=os.getenv('EMAIL_HOST'), + MAIL_PORT=os.getenv('EMAIL_PORT'), + MAIL_USE_SSL=True, + MAIL_USERNAME=os.getenv('EMAIL_USERNAME'), + MAIL_PASSWORD=os.getenv('EMAIL_PASSWORD'), + MAIL_DEFAULT_SENDER=(os.getenv('EMAIL_FROM_NAME'), os.getenv('EMAIL_FROM')) +) +mail = Mail(app) -# 配置日志 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_function.add_admin_routes(app, mail, bcrypt) +app_function.add_teacher_routes(app, mail, bcrypt) @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 - + return app_function._build_cors_preflight_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/app.txt b/app.txt deleted file mode 100644 index 19779eb..0000000 --- a/app.txt +++ /dev/null @@ -1,223 +0,0 @@ -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/app_function.py b/app_function.py new file mode 100644 index 0000000..36c1e8e --- /dev/null +++ b/app_function.py @@ -0,0 +1,781 @@ +from flask import Flask, request, jsonify, redirect, url_for, render_template, session, make_response, flash +from flask_mail import Mail, Message +from flask_bcrypt import Bcrypt +import mysql.connector +import boto3 +from botocore.client import Config +import os +import logging +import random +import pandas as pd +from dotenv import load_dotenv +from flask import send_file +import io +from functools import wraps + +load_dotenv() +bcrypt = Bcrypt() + +# 配置 AWS S3 +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') +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') +) + +# 数据库连接函数 +def get_db_connection(): + return mysql.connector.connect( + host=os.getenv('MYSQL_HOST'), + user=os.getenv('MYSQL_USER'), + password=os.getenv('MYSQL_PASSWORD'), + database=os.getenv('MYSQL_DB') + ) + +# 验证学生身份 +def validate_student(student_id, password, bcrypt): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM students WHERE id = %s', (student_id,)) + student = cursor.fetchone() + cursor.close() + conn.close() + if student and bcrypt.check_password_hash(student['password'], password): + return student + return None + +# 验证管理员身份 +def validate_admin(username, password): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM administrators WHERE username = %s', (username,)) + admin = cursor.fetchone() + cursor.close() + conn.close() + + if admin and bcrypt.check_password_hash(admin['password'], password): + return admin + return None + +# 验证教师身份 +def validate_teacher(email, password, bcrypt): + conn = get_db_connection() # 或者其他方法来获取数据库连接 + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM teachers WHERE email = %s', (email,)) + teacher = cursor.fetchone() + cursor.close() + conn.close() + + if teacher and bcrypt.check_password_hash(teacher['password'], password): + return teacher + return None + +def fetch_all_departments(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM departments') + departments = cursor.fetchall() + cursor.close() + conn.close() + return departments + +def fetch_all_grades(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM grades') + grades = cursor.fetchall() + cursor.close() + conn.close() + return grades + +def fetch_all_classes(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM classes') + classes = cursor.fetchall() + cursor.close() + conn.close() + return classes + +def fetch_all_teachers(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM teachers') + teachers = cursor.fetchall() + cursor.close() + conn.close() + return teachers + +def fetch_teacher_classes(teacher_id): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + + # 假设每个教师和班级的关联存储在一个中间表 `class_teacher` 中 + cursor.execute(''' + SELECT c.id, c.name FROM classes c + JOIN class_teacher ct ON c.id = ct.class_id + WHERE ct.teacher_id = %s + ''', (teacher_id,)) + + classes = cursor.fetchall() + + cursor.close() + conn.close() + return classes + +def fetch_class_assignments(class_id): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + pivot_query = ''' + SELECT * FROM assignments + WHERE class_id = %s + ''' + cursor.execute(pivot_query, (class_id,)) + assignments = cursor.fetchall() + cursor.close() + conn.close() + return assignments + + +def fetch_class_students(class_id): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + + # 查询属于给定class_id的所有学生 + cursor.execute(''' + SELECT * FROM students + WHERE class_id = %s + ''', (class_id,)) + + students = cursor.fetchall() + + cursor.close() + conn.close() + return students + + +# 添加到提交历史表 +def add_to_submission_history(student_id, assignment_id, filename): + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute( + 'INSERT INTO submission_history (student_id, assignment_id, filename, submit_date) VALUES (%s, %s, %s, NOW())', + (student_id, assignment_id, filename) + ) + conn.commit() + cursor.close() + conn.close() + +# 添加或更新作业提交记录 +def add_or_update_submission(student_id, assignment_id, filename, code_verified=False): + add_to_submission_history(student_id, assignment_id, filename) # 保留历史记录 + + conn = get_db_connection() + cursor = conn.cursor() + if code_verified: + cursor.execute( + 'UPDATE submissions SET submit_date = NOW(), filename = %s WHERE student_id = %s AND assignment_id = %s', + (filename, student_id, assignment_id)) + else: + cursor.execute( + 'INSERT INTO submissions (student_id, assignment_id, filename, submit_date) VALUES (%s, %s, %s, NOW())', + (student_id, assignment_id, filename)) + conn.commit() + cursor.close() + conn.close() + +# 生成预签名URL +def generate_presigned_url(object_key, content_type, expiration=3600): + try: + url = s3_client.generate_presigned_url( + 'put_object', + Params={'Bucket': bucket_name, 'Key': object_key, 'ContentType': content_type}, + ExpiresIn=expiration + ) + return url + except Exception as e: + logging.error(f"Failed to generate presigned URL: {str(e)}") + return None + +# 添加管理员路由 +def add_admin_routes(app, mail, bcrypt): + # 管理员登录装饰器 + def admin_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'admin_id' not in session: + return redirect(url_for('admin_login')) + return f(*args, **kwargs) + return decorated_function + + @app.route('/admin/add_department', methods=['POST']) + @admin_required + def add_department(): + name = request.form['name'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO departments (name) VALUES (%s)', (name,)) + conn.commit() + cursor.close() + conn.close() + flash('Department added successfully', 'success') + return redirect(url_for('admin_panel')) + + @app.route('/admin/add_grade', methods=['POST']) + @admin_required + def add_grade(): + year = request.form['year'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO grades (year) VALUES (%s)', (year,)) + conn.commit() + cursor.close() + conn.close() + flash('Grade added successfully', 'success') + return redirect(url_for('admin_panel')) + + @app.route('/admin/add_class', methods=['POST']) + @admin_required + def add_class(): + name = request.form['name'] + department_id = request.form['department_id'] + grade_id = request.form['grade_id'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO classes (name, department_id, grade_id) VALUES (%s, %s, %s)', + (name, department_id, grade_id)) + conn.commit() + cursor.close() + conn.close() + flash('Class added successfully', 'success') + return redirect(url_for('admin_panel')) + + @app.route('/admin/add_teacher', methods=['POST']) + @admin_required + def add_teacher(): + name = request.form['name'] + email = request.form['email'] + password = bcrypt.generate_password_hash(request.form['password']).decode('utf-8') + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO teachers (name, email, password) VALUES (%s, %s, %s)', + (name, email, password)) + conn.commit() + cursor.close() + conn.close() + flash('Teacher added successfully', 'success') + return redirect(url_for('admin_panel')) + + @app.route('/admin/add_administrator', methods=['POST']) + @admin_required + def add_administrator(): + username = request.form['username'] + password = bcrypt.generate_password_hash(request.form['password']).decode('utf-8') + teacher_id = request.form['teacher_id'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO administrators (username, password, teacher_id) VALUES (%s, %s, %s)', + (username, password, teacher_id)) + conn.commit() + cursor.close() + conn.close() + flash('Administrator added successfully', 'success') + return redirect(url_for('admin_panel')) + + @app.route('/admin/assign_teacher', methods=['POST']) + @admin_required + def assign_teacher(): + class_id = request.form['class_id'] + teacher_id = request.form['teacher_id'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO class_teacher (class_id, teacher_id) VALUES (%s, %s)', (class_id, teacher_id)) + conn.commit() + cursor.close() + conn.close() + flash('Teacher assigned to class successfully', 'success') + return redirect(url_for('admin_panel')) + + @app.route('/admin/login', methods=['GET', 'POST']) + def admin_login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + admin = validate_admin(username, password) + if admin: + session['admin_id'] = admin['id'] + return redirect(url_for('admin_panel')) + else: + flash('Invalid credentials', 'error') + return render_template('admin_login.html') + + @app.route('/admin/logout') + def admin_logout(): + session.pop('admin_id', None) + return redirect(url_for('admin_login')) + + @app.route('/admin/panel') + @admin_required + def admin_panel(): + # Retrieve necessary data (e.g., departments, grades, classes, etc.) + departments = fetch_all_departments() + grades = fetch_all_grades() + classes = fetch_all_classes() + teachers = fetch_all_teachers() + return render_template('admin_panel.html', departments=departments, grades=grades, classes=classes, teachers=teachers) + + @app.route('/admin/add_assignment', methods=['POST']) + @admin_required + def admin_add_assignment(): + value = request.form['value'] + name = request.form['name'] + deadline = request.form['deadline'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO assignments (value, name, deadline) VALUES (%s, %s, %s)', (value, name, deadline)) + conn.commit() + cursor.close() + conn.close() + flash('Assignment added successfully', 'success') + return redirect(url_for('admin_panel')) + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + student_id = request.form.get('student_id') + password = request.form.get('password') + student = validate_student(student_id, password, bcrypt) + if student: + session['student_id'] = student['id'] + session['student_name'] = student['name'] + return redirect(url_for('serve_index')) + else: + return render_template('login.html', error='学号或密码错误') + return render_template('login.html') + + @app.route('/reset-password', methods=['GET', 'POST']) + def reset_password(): + if request.method == 'POST': + student_id = request.form.get('student_id') + email = request.form.get('email') + code = request.form.get('code') + new_password = request.form.get('new_password') + if code: + if session.get('reset_code') == code and session.get('reset_student_id') == student_id: + conn = get_db_connection() + cursor = conn.cursor() + hashed_password = bcrypt.generate_password_hash(new_password).decode('utf-8') + cursor.execute('UPDATE students SET password = %s WHERE id = %s', (hashed_password, student_id)) + conn.commit() + cursor.close() + conn.close() + return render_template('login.html', success='密码已成功重置,请使用新密码登录') + else: + return render_template('reset_password.html', error='验证码错误') + else: + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM students WHERE id = %s AND email = %s', (student_id, email)) + student = cursor.fetchone() + cursor.close() + conn.close() + if student: + reset_code = ''.join(random.choices('0123456789', k=6)) + session['reset_code'] = reset_code + session['reset_student_id'] = student_id + try: + msg = Message('重置密码验证码', recipients=[email]) + msg.body = f'您用于重置密码的验证码是: {reset_code}' + mail.send(msg) + return render_template('reset_password.html', + success='验证码已发送到您的邮箱,请检查并输入验证码') + except Exception as e: + logging.error(f"Error sending email: {str(e)}") + return render_template('reset_password.html', error='发送验证码失败,请稍后再试') + else: + return render_template('reset_password.html', error='学号和邮箱不匹配') + return render_template('reset_password.html') + + @app.route('/record-submission', methods=['POST']) + def record_submission(): + data = request.json + student_id = session.get('student_id') + student_name = session.get('student_name') + assignment = data.get('assignment') + filename = data.get('filename') + + if not student_id or not filename or not assignment: + return jsonify({'error': 'Student_id, assignment and filename parameters are required'}), 400 + + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM submissions WHERE student_id = %s AND assignment_id = %s', + (student_id, assignment)) + submission = cursor.fetchone() + cursor.close() + conn.close() + + if submission: + session['filename'] = filename # 将文件名保存到session + return jsonify({'error': '作业已提交过,需要验证码'}), 401 + + # 生成 pre-signed URL 并记录提交 + new_filename = f'{student_id}_{student_name}_{assignment}' + folder_name = f'sure_homework_define_by_qin/{assignment}' + object_key = f'{folder_name}/{new_filename}' + url = generate_presigned_url(object_key, 'application/octet-stream') + + if not url: + logging.error("Failed to generate presigned URL") + return jsonify({'error': 'Failed to generate presigned URL'}), 500 + + add_or_update_submission(student_id, assignment, filename, code_verified=False) + return jsonify({'status': 'success', 'upload_url': url}) + + @app.route('/generate-code', methods=['POST']) + def generate_code(): + student_id = session.get('student_id') + assignment = request.json.get('assignment') + + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT email FROM students WHERE id = %s', (student_id,)) + student = cursor.fetchone() + cursor.close() + conn.close() + + if not student: + return jsonify({'error': '学生信息未找到'}), 404 + + email = student['email'] + reset_code = ''.join(random.choices('0123456789', k=6)) + session['submission_code'] = reset_code + session['submission_student_id'] = student_id + session['submission_assignment'] = assignment + + try: + msg = Message('提交作业验证码', recipients=[email]) + msg.body = f'您用于提交作业的验证码是: {reset_code}' + mail.send(msg) + return jsonify({'status': '验证码已发送到您的邮箱,请检查并输入验证码'}) + except Exception as e: + logging.error(f"Error sending email: {str(e)}") + return jsonify({'error': '发送验证码失败,请稍后再试'}), 500 + + @app.route('/validate-code', methods=['POST']) + def validate_code(): + request_data = request.json + student_id = session.get('student_id') + assignment = request_data.get('assignment') + code = request_data.get('code') + + if (code == session.get('submission_code') and + student_id == session.get('submission_student_id') and + assignment == session.get('submission_assignment')): + + filename = session.pop('filename', None) + new_filename = f'{student_id}_{session.get("student_name")}_{assignment}' + folder_name = f'sure_homework_define_by_qin/{assignment}' + object_key = f'{folder_name}/{new_filename}' + + logging.info(f"Generated object_key: {object_key}") + + url = generate_presigned_url(object_key, 'application/octet-stream') + + if not url: + logging.error("Failed to generate presigned URL") + return jsonify({'error': 'Failed to generate presigned URL'}), 500 + + # 调用 add_or_update_submission 函数来更新数据库记录 + add_or_update_submission(student_id, assignment, filename, code_verified=True) + + # 删除session中的凭据 + session.pop('submission_code', None) + session.pop('submission_student_id', None) + session.pop('submission_assignment', None) + session['validated_assignment'] = assignment + session['validation_presigned_url'] = url + return jsonify({'status': '成功', 'upload_url': url}) + else: + return jsonify({'error': '验证码错误'}), 400 + + @app.route('/api/submissions') + def get_submissions(): + try: + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute(""" + SELECT + sh.submit_date AS 时间, + sh.filename AS 提交的文件, + s.name AS 姓名, + s.id AS 学号, + sh.assignment_id AS 作业 + FROM submission_history sh + JOIN students s ON sh.student_id = s.id + ORDER BY sh.submit_date DESC + """) + submissions = cursor.fetchall() + cursor.close() + conn.close() + + # 格式化日期时间 + for sub in submissions: + sub['时间'] = sub['时间'].strftime('%Y-%m-%d %H:%M:%S') if sub['时间'] else None + + return jsonify(submissions) + except Exception as e: + logging.error(f"Error in get_submissions: {str(e)}", exc_info=True) + return jsonify({'error': str(e)}), 500 + + @app.route('/api/assignment-status') + def get_assignment_status(): + try: + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute(""" + SELECT + s.id AS 学号, + s.name AS 姓名, + a.assignment_id AS 作业, + CASE WHEN sub.id IS NOT NULL THEN '已提交' ELSE '未提交' END AS 提交情况, + COALESCE(sub.filename, '') AS 提交的文件 + FROM students s + CROSS JOIN (SELECT DISTINCT assignment_id FROM submissions) a + LEFT JOIN submissions sub ON s.id = sub.student_id AND a.assignment_id = sub.assignment_id + ORDER BY s.id, a.assignment_id + """) + status_data = cursor.fetchall() + cursor.close() + conn.close() + + return jsonify(status_data) + except Exception as e: + logging.error(f"Error in get_assignment_status: {str(e)}", exc_info=True) + return jsonify({'error': str(e)}), 500 + + @app.route('/download-submissions') + def download_submissions(): + try: + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute(""" + SELECT + sh.submit_date AS 时间, + sh.filename AS 提交的项目, + s.name AS 姓名, + s.id AS 学号, + sh.assignment_id AS 作业 + FROM submission_history sh + JOIN students s ON sh.student_id = s.id + ORDER BY sh.submit_date DESC + """) + submissions = cursor.fetchall() + cursor.close() + conn.close() + + # 创建 DataFrame + df = pd.DataFrame(submissions) + + # 格式化日期时间 + df['时间'] = pd.to_datetime(df['时间']).dt.strftime('%Y-%m-%d %H:%M:%S') + + # 确保列的顺序 + df = df[['时间', '提交的项目', '姓名', '学号', '作业']] + + # 创建一个 BytesIO 对象,用于存储 Excel 文件 + output = io.BytesIO() + + # 使用 ExcelWriter 来设置列宽 + with pd.ExcelWriter(output, engine='xlsxwriter') as writer: + df.to_excel(writer, sheet_name='提交记录', index=False) + worksheet = writer.sheets['提交记录'] + + # 设置列宽 + worksheet.set_column('A:A', 20) # 时间 + worksheet.set_column('B:B', 30) # 提交的项目 + worksheet.set_column('C:C', 15) # 姓名 + worksheet.set_column('D:D', 15) # 学号 + worksheet.set_column('E:E', 20) # 作业 + + output.seek(0) + + return send_file( + output, + as_attachment=True, + download_name='submissions.xlsx', + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ) + except Exception as e: + logging.error(f"Error in download_submissions: {str(e)}", exc_info=True) + return jsonify({'error': str(e)}), 500 + + @app.route('/download-assignment-status') + def download_assignment_status(): + try: + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute(""" + SELECT + s.id AS 学号, + s.name AS 姓名, + COALESCE(sub.assignment_id, 'No assignment') AS 作业, + CASE WHEN sub.id IS NOT NULL THEN '已提交' ELSE '未提交' END AS 作业提交情况, + COALESCE(sub.filename, '') AS 提交的项目, + COALESCE(sub.submit_date, '') AS 提交时间 + FROM students s + LEFT JOIN submissions sub ON s.id = sub.student_id + ORDER BY s.id, sub.assignment_id + """) + status_data = cursor.fetchall() + cursor.close() + conn.close() + + df = pd.DataFrame(status_data) + + # 格式化日期时间 + df['提交时间'] = pd.to_datetime(df['提交时间']).dt.strftime('%Y-%m-%d %H:%M:%S') + + # 确保列的顺序 + df = df[['学号', '姓名', '作业', '作业提交情况', '提交的项目', '提交时间']] + + output = io.BytesIO() + with pd.ExcelWriter(output, engine='xlsxwriter') as writer: + df.to_excel(writer, sheet_name='作业提交情况', index=False) + worksheet = writer.sheets['作业提交情况'] + + # 设置列宽 + worksheet.set_column('A:A', 15) # 学号 + worksheet.set_column('B:B', 10) # 姓名 + worksheet.set_column('C:C', 15) # 作业 + worksheet.set_column('D:D', 12) # 作业提交情况 + worksheet.set_column('E:E', 30) # 提交的项目 + worksheet.set_column('F:F', 20) # 提交时间 + + output.seek(0) + + return send_file( + output, + as_attachment=True, + download_name='assignment_status.xlsx', + mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ) + except Exception as e: + logging.error(f"Error in download_assignment_status: {str(e)}", exc_info=True) + return jsonify({'error': str(e)}), 500 + + @app.route('/') + def serve_index(): + if 'student_id' not in session: + return redirect(url_for('login')) + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM assignments ORDER BY deadline') + assignments = cursor.fetchall() + cursor.close() + conn.close() + return render_template('index.html', assignments=assignments) + + @app.route('/logout') + def logout(): + session.clear() + return redirect(url_for('login')) + + 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 + +# 教师管理路由 +def add_teacher_routes(app, mail, bcrypt): + # 教师登录装饰器 + def teacher_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'teacher_id' not in session: + return redirect(url_for('teacher_login')) + return f(*args, **kwargs) + return decorated_function + + # Teacher Login Route + @app.route('/teacher/login', methods=['GET', 'POST']) + def teacher_login(): + if request.method == 'POST': + email = request.form['email'] + password = request.form['password'] + teacher = validate_teacher(email, password, bcrypt) + if teacher: + session['teacher_id'] = teacher['id'] + return redirect(url_for('teacher_panel')) + else: + flash('Invalid credentials', 'error') + return render_template('teacher_login.html') + + @app.route('/teacher/logout') + def teacher_logout(): + session.pop('teacher_id', None) + return redirect(url_for('teacher_login')) + + @app.route('/teacher/panel') + @teacher_required + def teacher_panel(): + # Fetch the classes this teacher is responsible for + teacher_id = session['teacher_id'] + classes = fetch_teacher_classes(teacher_id) + return render_template('teacher_panel.html', classes=classes) + + @app.route('/teacher/class/') + @teacher_required + def view_class(class_id): + # Fetch the assignments and students of the class + assignments = fetch_class_assignments(class_id) + students = fetch_class_students(class_id) + return render_template('class_detail.html', assignments=assignments, students=students, class_id=class_id) + + @app.route('/teacher/class//add_assignment', methods=['POST']) + @teacher_required + def teacher_add_assignment(class_id): + # Adding a new assignment to the class + value = request.form['value'] + name = request.form['name'] + deadline = request.form['deadline'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('INSERT INTO assignments (value, name, deadline, class_id) VALUES (%s, %s, %s, %s)', (value, name, deadline, class_id)) + conn.commit() + cursor.close() + conn.close() + flash('Assignment added successfully', 'success') + return redirect(url_for('view_class', class_id=class_id)) + + @app.route('/teacher/edit_assignment/', methods=['POST']) + def edit_assignment(assignment_id): + new_deadline = request.json.get('deadline') + if new_deadline: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('UPDATE assignments SET deadline = %s WHERE id = %s', (new_deadline, assignment_id)) + conn.commit() + cursor.close() + conn.close() + return jsonify({'status': 'success'}) + return jsonify({'error': 'Invalid deadline'}), 400 + + @app.route('/teacher/delete_assignment/', methods=['DELETE']) + def delete_assignment(assignment_id): + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('DELETE FROM assignments WHERE id = %s', (assignment_id,)) + conn.commit() + cursor.close() + conn.close() + return jsonify({'status': 'success'}) + diff --git a/ccc.py b/ccc.py new file mode 100644 index 0000000..c8aae87 --- /dev/null +++ b/ccc.py @@ -0,0 +1,69 @@ +import os +from docx import Document + + +def find_files(start_path, extensions): + """ + 遍历目录并找到指定扩展名的文件。 + + :param start_path: 起始路径 + :param extensions: 需要查找的文件扩展名列表 + :return: 文件路径列表 + """ + file_list = [] + for root, dirs, files in os.walk(start_path): + for file in files: + if any(file.endswith(ext) for ext in extensions): + file_list.append(os.path.join(root, file)) + return file_list + + +def read_files(file_list): + """ + 读取文件内容。 + + :param file_list: 文件路径列表 + :return: 文件内容字典 + """ + content_dict = {} + for file_path in file_list: + with open(file_path, 'r', encoding='utf-8') as file: + content_dict[file_path] = file.read() + return content_dict + + +def save_to_docx(content_dict, output_file): + """ + 将文件内容字典保存到 DOCX 文件。 + + :param content_dict: 文件内容字典 + :param output_file: 输出的 DOCX 文件名 + """ + doc = Document() + for file_path, content in content_dict.items(): + doc.add_heading(file_path, level=1) + doc.add_paragraph(content) + doc.add_page_break() # 添加分页符 + doc.save(output_file) + + +if __name__ == "__main__": + # 需要遍历的目录 + directory = '/Users/lishunqin/Desktop/study/pychram project/AWS-sure' + + # 需要查找的文件扩展名 + extensions = ['.py', '.html', '.env', '.css', 'js'] + + # 查找文件 + files = find_files(directory, extensions) + + # 读取文件内容 + content_dict = read_files(files) + + # 输出 DOCX 文件名 + output_docx = 'output_files_content.docx' + + # 保存到 DOCX 文件 + save_to_docx(content_dict, output_docx) + + print(f"找到 {len(files)} 个文件,并保存了内容到 {output_docx}") diff --git a/database.py b/database.py new file mode 100644 index 0000000..5c9840a --- /dev/null +++ b/database.py @@ -0,0 +1,280 @@ +import os +import mysql.connector +from flask import Flask, request, jsonify, session, redirect, url_for, render_template +from flask_bcrypt import Bcrypt +from some_module_to_verify_code import verify_code # 假设你有相应的模块 +import datetime +import random +import logging +from flask_mail import Mail, Message +import boto3 +from botocore.client import Config +import csv + +# 初始化 Flask 和 Bcrypt +app = Flask(__name__) +bcrypt = Bcrypt(app) +mail = Mail(app) + +# 配置日志 +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') + +# 配置 AWS S3 +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') + +# 初始化S3客户端 +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') +) + +# 初始化 CSV 文件 +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 get_db_connection(): + return mysql.connector.connect( + host='8.218.165.242', + user='sure_001', + password='EKKWLMmrGmG7sdPf', + database='sure_001' + ) + + +# 验证学生身份 +def validate_student(student_id, password): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM students WHERE id = %s', (student_id,)) + student = cursor.fetchone() + cursor.close() + conn.close() + if student and bcrypt.check_password_hash(student['password'], password): + return student + return None + + +# 检查作业提交 +def check_submission(student_id, assignment_id): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM submissions WHERE student_id = %s AND assignment_id = %s', + (student_id, assignment_id)) + submission = cursor.fetchone() + cursor.close() + conn.close() + return submission + + +# 添加或更新作业提交 +def add_or_update_submission(student_id, assignment_id, filename, code_verified=False): + conn = get_db_connection() + cursor = conn.cursor() + if code_verified: + cursor.execute( + 'UPDATE submissions SET submit_date = NOW(), filename = %s WHERE student_id = %s AND assignment_id = %s', + (filename, student_id, assignment_id)) + else: + cursor.execute( + 'INSERT INTO submissions (student_id, assignment_id, filename, submit_date) VALUES (%s, %s, %s, NOW())', + (student_id, assignment_id, filename)) + conn.commit() + cursor.close() + conn.close() + +def fetch_all_departments(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM departments') + departments = cursor.fetchall() + cursor.close() + conn.close() + return departments + +def fetch_all_grades(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM grades') + grades = cursor.fetchall() + cursor.close() + conn.close() + return grades + +def fetch_all_classes(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM classes') + classes = cursor.fetchall() + cursor.close() + conn.close() + return classes + +def fetch_all_teachers(): + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM teachers') + teachers = cursor.fetchall() + cursor.close() + conn.close() + return teachers + + + +# 生成预签名URL +def generate_presigned_url(object_key, content_type): + try: + url = s3_client.generate_presigned_url( + 'put_object', + Params={'Bucket': bucket_name, 'Key': object_key, 'ContentType': content_type}, + ExpiresIn=3600 + ) + return url + except Exception as e: + logging.error(f"Failed to generate presigned URL: {str(e)}") + return None + + +# 提交作业路由 +@app.route('/submit-assignment', methods=['POST']) +def submit_assignment(): + student_id = request.form.get('student_id') + assignment_id = request.form.get('assignment_id') + filename = request.form.get('filename') + + submission = check_submission(student_id, assignment_id) + + if submission: + # 要求用户输入验证码 + email = request.form.get('email') + code = request.form.get('code') + if verify_code(email, code): + add_or_update_submission(student_id, assignment_id, filename, code_verified=True) + else: + add_or_update_submission(student_id, assignment_id, filename, code_verified=False) + + return 'Submission successful' + + +# 登录路由 +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + student_id = request.form.get('student_id') + password = request.form.get('password') + student = validate_student(student_id, password) + if student: + session['student_id'] = student['id'] + session['student_name'] = student['name'] + return redirect(url_for('serve_index')) + else: + return render_template('login.html', error='学号或密码错误') + return render_template('login.html') + + +# 重置密码路由 +@app.route('/reset-password', methods=['GET', 'POST']) +def reset_password(): + if request.method == 'POST': + student_id = request.form.get('student_id') + email = request.form.get('email') + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT * FROM students WHERE id = %s AND email = %s', (student_id, email)) + student = cursor.fetchone() + cursor.close() + conn.close() + + if student: + reset_code = ''.join(random.choices('0123456789', k=6)) + session['reset_code'] = reset_code + session['reset_student_id'] = student_id + try: + msg = Message('重置密码验证码', recipients=[email]) + msg.body = f'您用于重置密码的验证码是: {reset_code}' + mail.send(msg) + return render_template('reset_password.html', success='验证码已发送到您的邮箱,请检查并输入验证码') + except Exception as e: + logging.error(f"Error sending email: {str(e)}") + return render_template('reset_password.html', error='发送验证码失败,请稍后再试') + else: + return render_template('reset_password.html', error='学号和邮箱不匹配') + return render_template('reset_password.html') + + +# 提交记录路由 +@app.route('/record-submission', methods=['POST']) +def record_submission(): + data = request.json + student_id = session.get('student_id') + student_name = session.get('student_name') + assignment = data.get('assignment') + filename = data.get('filename') + + if not student_id or not filename or not assignment: + return jsonify({'error': 'Student_id, assignment and filename parameters are required'}), 400 + + # Check if the student has already submitted this assignment + submission = check_submission(student_id, assignment) + if submission: + session['filename'] = filename + return jsonify({'error': '作业已提交过,需要验证码'}), 401 + + # 生成 pre-signed URL 并记录提交 + new_filename = f'{student_id}_{student_name}_{assignment}' + folder_name = f'sure_homework_define_by_qin/{assignment}' + object_key = f'{folder_name}/{new_filename}' + url = generate_presigned_url(object_key, 'application/octet-stream') + + if not url: + logging.error("Failed to generate presigned URL") + return jsonify({'error': 'Failed to generate presigned URL'}), 500 + + add_or_update_submission(student_id, assignment, filename) + return jsonify({'status': 'success', 'upload_url': url}) + + +# 生成验证码路由 +@app.route('/generate-code', methods=['POST']) +def generate_code(): + student_id = session.get('student_id') + assignment = request.json.get('assignment') + + conn = get_db_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute('SELECT email FROM students WHERE id = %s', (student_id,)) + student = cursor.fetchone() + cursor.close() + conn.close() + + if not student: + return jsonify({'error': '学生信息未找到'}), 404 + + email = student['email'] + reset_code = ''.join(random.choices('0123456789', k=6)) + session['submission_code'] = reset_code + session['submission_student_id'] = student_id + session['submission_assignment'] = assignment + + try: + msg = Message('提交验证码', recipients=[email]) + msg.body = f'您用于提交作业的验证码是: {reset_code}' + mail.send(msg) + return jsonify({'status': '验证码已发送到您的邮箱'}) + except Exception as e: + logging.error(f"Error sending email: {str(e)}") + return jsonify({'error': '发送验证码失败,请稍后再试'}), 500 + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/edit_admin_info.py b/edit_admin_info.py new file mode 100644 index 0000000..d19315f --- /dev/null +++ b/edit_admin_info.py @@ -0,0 +1,46 @@ +from flask_bcrypt import Bcrypt +import mysql.connector + +# Initialize Bcrypt +bcrypt = Bcrypt() + +# Database connection details +MYSQL_HOST = '8.218.165.242' +MYSQL_USER = 'sure_001' +MYSQL_PASSWORD = 'EKKWLMmrGmG7sdPf' +MYSQL_DB = 'sure_001' + +# Function to get database connection +def get_db_connection(): + return mysql.connector.connect( + host=MYSQL_HOST, + user=MYSQL_USER, + password=MYSQL_PASSWORD, + database=MYSQL_DB + ) + +# Function to update admin password +def update_teacher_password(teacher_id, new_plain_password): + conn = get_db_connection() + cursor = conn.cursor() + + # Generate new hashed password + hashed_password = bcrypt.generate_password_hash(new_plain_password).decode('utf-8') + + # Update password in the database + cursor.execute( + 'UPDATE teachers SET password = %s WHERE id = %s', + (hashed_password, teacher_id) + ) + + # Commit changes and close the connection + conn.commit() + cursor.close() + conn.close() + +# Example: Updating the password for admin with ID 1 +if __name__ == "__main__": + tid = 1 # The ID of the admin to update + new_password = 'xiaoyan99817' # The new plain password you want to set + update_teacher_password(tid, new_password) + print(f"Password for admin with ID {tid} has been updated.") diff --git a/flask_session/0f9028b75157442ffeb13d3d89859e37 b/flask_session/0f9028b75157442ffeb13d3d89859e37 new file mode 100644 index 0000000..bf1eb9c Binary files /dev/null and b/flask_session/0f9028b75157442ffeb13d3d89859e37 differ diff --git a/flask_session/121e2e6237f93addcf2c8d3d0f754de5 b/flask_session/121e2e6237f93addcf2c8d3d0f754de5 new file mode 100644 index 0000000..ced0b30 Binary files /dev/null and b/flask_session/121e2e6237f93addcf2c8d3d0f754de5 differ diff --git a/flask_session/1bcc7dc1ae94d369f266fc6c335f304f b/flask_session/1bcc7dc1ae94d369f266fc6c335f304f new file mode 100644 index 0000000..440f873 Binary files /dev/null and b/flask_session/1bcc7dc1ae94d369f266fc6c335f304f differ diff --git a/flask_session/1dd18bd3439eb0ac9a5742e2231f6172 b/flask_session/1dd18bd3439eb0ac9a5742e2231f6172 new file mode 100644 index 0000000..e4c0bc3 Binary files /dev/null and b/flask_session/1dd18bd3439eb0ac9a5742e2231f6172 differ diff --git a/flask_session/2029240f6d1128be89ddc32729463129 b/flask_session/2029240f6d1128be89ddc32729463129 new file mode 100644 index 0000000..bf37ad6 Binary files /dev/null and b/flask_session/2029240f6d1128be89ddc32729463129 differ diff --git a/flask_session/230a5611f6ae17aede03b96deb47fff3 b/flask_session/230a5611f6ae17aede03b96deb47fff3 new file mode 100644 index 0000000..6468b0d Binary files /dev/null and b/flask_session/230a5611f6ae17aede03b96deb47fff3 differ diff --git a/flask_session/32dcb849af4ca45626677d9fb3bd4768 b/flask_session/32dcb849af4ca45626677d9fb3bd4768 new file mode 100644 index 0000000..5a289aa Binary files /dev/null and b/flask_session/32dcb849af4ca45626677d9fb3bd4768 differ diff --git a/flask_session/3378eb0eec6e11aefe392c5e676e8238 b/flask_session/3378eb0eec6e11aefe392c5e676e8238 new file mode 100644 index 0000000..d393183 Binary files /dev/null and b/flask_session/3378eb0eec6e11aefe392c5e676e8238 differ diff --git a/flask_session/35f86e813ab1fdd9f718aef70f2447b1 b/flask_session/35f86e813ab1fdd9f718aef70f2447b1 new file mode 100644 index 0000000..3689e9a Binary files /dev/null and b/flask_session/35f86e813ab1fdd9f718aef70f2447b1 differ diff --git a/flask_session/37ddbf9efe72bdf64d50c9a4ca92bc2e b/flask_session/37ddbf9efe72bdf64d50c9a4ca92bc2e new file mode 100644 index 0000000..113f7b7 Binary files /dev/null and b/flask_session/37ddbf9efe72bdf64d50c9a4ca92bc2e differ diff --git a/flask_session/3ba267ea541c85852c864ec0a198c77b b/flask_session/3ba267ea541c85852c864ec0a198c77b new file mode 100644 index 0000000..c1f3841 Binary files /dev/null and b/flask_session/3ba267ea541c85852c864ec0a198c77b differ diff --git a/flask_session/3d315f20ae346ffe9e4a7b40ce7fef37 b/flask_session/3d315f20ae346ffe9e4a7b40ce7fef37 new file mode 100644 index 0000000..843f3e7 Binary files /dev/null and b/flask_session/3d315f20ae346ffe9e4a7b40ce7fef37 differ diff --git a/flask_session/3eafc0e8b084dcd91b81fbed0438bade b/flask_session/3eafc0e8b084dcd91b81fbed0438bade new file mode 100644 index 0000000..25b8516 Binary files /dev/null and b/flask_session/3eafc0e8b084dcd91b81fbed0438bade differ diff --git a/flask_session/4661f790eca664fbdf71fa25c7c45a1e b/flask_session/4661f790eca664fbdf71fa25c7c45a1e new file mode 100644 index 0000000..b893f42 Binary files /dev/null and b/flask_session/4661f790eca664fbdf71fa25c7c45a1e differ diff --git a/flask_session/4c88942d540c4b0697beb6fc71d0e515 b/flask_session/4c88942d540c4b0697beb6fc71d0e515 new file mode 100644 index 0000000..402ce9a Binary files /dev/null and b/flask_session/4c88942d540c4b0697beb6fc71d0e515 differ diff --git a/flask_session/4feab7ace0e99d4bd4371560894aaa18 b/flask_session/4feab7ace0e99d4bd4371560894aaa18 new file mode 100644 index 0000000..0d38b2c Binary files /dev/null and b/flask_session/4feab7ace0e99d4bd4371560894aaa18 differ diff --git a/flask_session/543c69174ee1c6787e710d1656d1672a b/flask_session/543c69174ee1c6787e710d1656d1672a new file mode 100644 index 0000000..4165c6f Binary files /dev/null and b/flask_session/543c69174ee1c6787e710d1656d1672a differ diff --git a/flask_session/5ef0a832f26e148c61d14e95f03b6a33 b/flask_session/5ef0a832f26e148c61d14e95f03b6a33 new file mode 100644 index 0000000..21006d0 Binary files /dev/null and b/flask_session/5ef0a832f26e148c61d14e95f03b6a33 differ diff --git a/flask_session/66d1db5c2c79975bb6a80d14fa67dbfe b/flask_session/66d1db5c2c79975bb6a80d14fa67dbfe new file mode 100644 index 0000000..f59cc43 Binary files /dev/null and b/flask_session/66d1db5c2c79975bb6a80d14fa67dbfe differ diff --git a/flask_session/6c904875ff094c84e40eb4cb5ba58586 b/flask_session/6c904875ff094c84e40eb4cb5ba58586 new file mode 100644 index 0000000..3c3d812 Binary files /dev/null and b/flask_session/6c904875ff094c84e40eb4cb5ba58586 differ diff --git a/flask_session/6cafcac3319918665aaf1772f04b8432 b/flask_session/6cafcac3319918665aaf1772f04b8432 new file mode 100644 index 0000000..6578cf1 Binary files /dev/null and b/flask_session/6cafcac3319918665aaf1772f04b8432 differ diff --git a/flask_session/6f977085e9c72d687e7a132ccfbf6f88 b/flask_session/6f977085e9c72d687e7a132ccfbf6f88 new file mode 100644 index 0000000..cca7fd6 Binary files /dev/null and b/flask_session/6f977085e9c72d687e7a132ccfbf6f88 differ diff --git a/flask_session/6fecf9b935f7299d56f4a6324bc1b56c b/flask_session/6fecf9b935f7299d56f4a6324bc1b56c new file mode 100644 index 0000000..6f778dc Binary files /dev/null and b/flask_session/6fecf9b935f7299d56f4a6324bc1b56c differ diff --git a/flask_session/70195a82b9eb0295dc9eb356241021e7 b/flask_session/70195a82b9eb0295dc9eb356241021e7 new file mode 100644 index 0000000..535a922 Binary files /dev/null and b/flask_session/70195a82b9eb0295dc9eb356241021e7 differ diff --git a/flask_session/777d3de3359fadd58752eff37b828bd3 b/flask_session/777d3de3359fadd58752eff37b828bd3 new file mode 100644 index 0000000..5e64640 Binary files /dev/null and b/flask_session/777d3de3359fadd58752eff37b828bd3 differ diff --git a/flask_session/7e37cff998297dd71a9e4b115e258f12 b/flask_session/7e37cff998297dd71a9e4b115e258f12 new file mode 100644 index 0000000..70e161d Binary files /dev/null and b/flask_session/7e37cff998297dd71a9e4b115e258f12 differ diff --git a/flask_session/870fe179fe41bf8bd2828724b80ca924 b/flask_session/870fe179fe41bf8bd2828724b80ca924 new file mode 100644 index 0000000..57ca09a Binary files /dev/null and b/flask_session/870fe179fe41bf8bd2828724b80ca924 differ diff --git a/flask_session/8f3bbee347151cfa1f698678c74250be b/flask_session/8f3bbee347151cfa1f698678c74250be new file mode 100644 index 0000000..bb24040 Binary files /dev/null and b/flask_session/8f3bbee347151cfa1f698678c74250be differ diff --git a/flask_session/90f85e48b946465ec01b5b9f7883ebc9 b/flask_session/90f85e48b946465ec01b5b9f7883ebc9 new file mode 100644 index 0000000..33627cf Binary files /dev/null and b/flask_session/90f85e48b946465ec01b5b9f7883ebc9 differ diff --git a/flask_session/95231a9ec2e3d104ce75bd0dd780d736 b/flask_session/95231a9ec2e3d104ce75bd0dd780d736 new file mode 100644 index 0000000..a6a71cc Binary files /dev/null and b/flask_session/95231a9ec2e3d104ce75bd0dd780d736 differ diff --git a/flask_session/95bb16dcb3c2e0f7a547ef0127e36165 b/flask_session/95bb16dcb3c2e0f7a547ef0127e36165 new file mode 100644 index 0000000..8b2f10c Binary files /dev/null and b/flask_session/95bb16dcb3c2e0f7a547ef0127e36165 differ diff --git a/flask_session/9b87431787e796d1ee5b357cca183fff b/flask_session/9b87431787e796d1ee5b357cca183fff new file mode 100644 index 0000000..471fbd9 Binary files /dev/null and b/flask_session/9b87431787e796d1ee5b357cca183fff differ diff --git a/flask_session/a7a737b402857b6db6a1d193fa8e823d b/flask_session/a7a737b402857b6db6a1d193fa8e823d new file mode 100644 index 0000000..73412d2 Binary files /dev/null and b/flask_session/a7a737b402857b6db6a1d193fa8e823d differ diff --git a/flask_session/b8202bf4ccda274d60e685c1d1f84ad2 b/flask_session/b8202bf4ccda274d60e685c1d1f84ad2 new file mode 100644 index 0000000..7a66f12 Binary files /dev/null and b/flask_session/b8202bf4ccda274d60e685c1d1f84ad2 differ diff --git a/flask_session/be31830b82505a6516b3904fdc7aeddf b/flask_session/be31830b82505a6516b3904fdc7aeddf new file mode 100644 index 0000000..2fcfa50 Binary files /dev/null and b/flask_session/be31830b82505a6516b3904fdc7aeddf differ diff --git a/flask_session/c1026cac75c91141eaf02c773ef7e6a4 b/flask_session/c1026cac75c91141eaf02c773ef7e6a4 new file mode 100644 index 0000000..0e480e3 Binary files /dev/null and b/flask_session/c1026cac75c91141eaf02c773ef7e6a4 differ diff --git a/flask_session/cbfce63dc9c13b155891bad9f6d33232 b/flask_session/cbfce63dc9c13b155891bad9f6d33232 new file mode 100644 index 0000000..e7bc3a9 Binary files /dev/null and b/flask_session/cbfce63dc9c13b155891bad9f6d33232 differ diff --git a/flask_session/ceaa5b464cc4cd0d247143a82f99a170 b/flask_session/ceaa5b464cc4cd0d247143a82f99a170 new file mode 100644 index 0000000..a05072c Binary files /dev/null and b/flask_session/ceaa5b464cc4cd0d247143a82f99a170 differ diff --git a/flask_session/cee8af23a1270f20c6b07ab921837c69 b/flask_session/cee8af23a1270f20c6b07ab921837c69 new file mode 100644 index 0000000..291042e Binary files /dev/null and b/flask_session/cee8af23a1270f20c6b07ab921837c69 differ diff --git a/flask_session/d1ab18f8f028a3781bf8a8cffc4d4f24 b/flask_session/d1ab18f8f028a3781bf8a8cffc4d4f24 new file mode 100644 index 0000000..3bdbf91 Binary files /dev/null and b/flask_session/d1ab18f8f028a3781bf8a8cffc4d4f24 differ diff --git a/flask_session/dfb04ede4cc875e045b57e7e1d40d602 b/flask_session/dfb04ede4cc875e045b57e7e1d40d602 new file mode 100644 index 0000000..57402b4 Binary files /dev/null and b/flask_session/dfb04ede4cc875e045b57e7e1d40d602 differ diff --git a/flask_session/e1f855cdb1992a356fe2d07262ff56a1 b/flask_session/e1f855cdb1992a356fe2d07262ff56a1 new file mode 100644 index 0000000..9814210 Binary files /dev/null and b/flask_session/e1f855cdb1992a356fe2d07262ff56a1 differ diff --git a/flask_session/e58189118050886d22b23b51e8651460 b/flask_session/e58189118050886d22b23b51e8651460 new file mode 100644 index 0000000..ecbf678 Binary files /dev/null and b/flask_session/e58189118050886d22b23b51e8651460 differ diff --git a/flask_session/e6d467bfcddb3a16f2ad5a28d8bd7d36 b/flask_session/e6d467bfcddb3a16f2ad5a28d8bd7d36 new file mode 100644 index 0000000..3143690 Binary files /dev/null and b/flask_session/e6d467bfcddb3a16f2ad5a28d8bd7d36 differ diff --git a/flask_session/e7f114dcf7f3868fb06d972494494b13 b/flask_session/e7f114dcf7f3868fb06d972494494b13 new file mode 100644 index 0000000..0e480e3 Binary files /dev/null and b/flask_session/e7f114dcf7f3868fb06d972494494b13 differ diff --git a/flask_session/ee13c570a79dffb0127559fd9e0dfca7 b/flask_session/ee13c570a79dffb0127559fd9e0dfca7 new file mode 100644 index 0000000..8177359 Binary files /dev/null and b/flask_session/ee13c570a79dffb0127559fd9e0dfca7 differ diff --git a/flask_session/f93924e17b9f01a5729553789b9857a5 b/flask_session/f93924e17b9f01a5729553789b9857a5 new file mode 100644 index 0000000..156ec7f Binary files /dev/null and b/flask_session/f93924e17b9f01a5729553789b9857a5 differ diff --git a/flask_session/fba9a66c8591f2a7d6baee089322217e b/flask_session/fba9a66c8591f2a7d6baee089322217e new file mode 100644 index 0000000..cb9148d Binary files /dev/null and b/flask_session/fba9a66c8591f2a7d6baee089322217e differ diff --git a/flask_session/feca8e10607ef23e8d1acfd652b20ac2 b/flask_session/feca8e10607ef23e8d1acfd652b20ac2 new file mode 100644 index 0000000..5687a33 Binary files /dev/null and b/flask_session/feca8e10607ef23e8d1acfd652b20ac2 differ diff --git a/generate_presigned_url.py b/generate_presigned_url.py deleted file mode 100644 index 5384d61..0000000 --- a/generate_presigned_url.py +++ /dev/null @@ -1,31 +0,0 @@ -import boto3 -from botocore.exceptions import NoCredentialsError, ClientError - -# 使用获取的访问密钥ID和秘密访问密钥来初始化S3客户端 -aws_access_key_id = 'AKIAZQ3DT3KLN5WGXZOR' # 替换为你的访问密钥ID -aws_secret_access_key = '5UZb8SovTrbroT7yU1pBzaR5myLn+NMA+c87RvLH' # 替换为你的秘密访问密钥 -region_name = 'ap-northeast-1' # 替换为你的S3存储桶所在区域 - -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 -) - -# 创建一个模拟“文件夹”的空对象 -def create_s3_folder(bucket_name, folder_name): - try: - s3_client.put_object(Bucket=bucket_name, Key=(folder_name + '/')) - print(f"Folder {folder_name} created in bucket {bucket_name}") - except NoCredentialsError: - print("Credentials not available") - except ClientError as e: - print(f"Error: {e}") - -# Bucket name and folder name -bucket_name = 'sure-ae-upload' -folder_name = 'sure_homework_define_by_qin' - -# Create folder -create_s3_folder(bucket_name, folder_name) diff --git a/generate_secret_key.py b/generate_secret_key.py new file mode 100644 index 0000000..25e0d10 --- /dev/null +++ b/generate_secret_key.py @@ -0,0 +1,2 @@ +import secrets +print(secrets.token_hex(16)) # 生成一个32字符长的随机字符串 diff --git a/index.html b/index.html deleted file mode 100644 index 6d639cb..0000000 --- a/index.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - 秀儿文件提交系统 - - - -
-
- 描述性文本 -
-

秀儿文件提交系统

-
-
- - -
-
- - -
-
- - -
-
- -
- -
-
未选择文件
-
- -
-
-
-
-
-
-
0%
-
- 速度: 0 KB/s - 0 KB / 0 KB - 剩余时间: 计算中 -
-
- 下载统计表格 -
- - - - - - diff --git a/index.txt b/index.txt deleted file mode 100644 index 6d639cb..0000000 --- a/index.txt +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - 秀儿文件提交系统 - - - -
-
- 描述性文本 -
-

秀儿文件提交系统

-
-
- - -
-
- - -
-
- - -
-
- -
- -
-
未选择文件
-
- -
-
-
-
-
-
-
0%
-
- 速度: 0 KB/s - 0 KB / 0 KB - 剩余时间: 计算中 -
-
- 下载统计表格 -
- - - - - - diff --git a/insert_data_to_database.py b/insert_data_to_database.py new file mode 100644 index 0000000..a6e9a08 --- /dev/null +++ b/insert_data_to_database.py @@ -0,0 +1,39 @@ +import json +import mysql.connector +from flask_bcrypt import Bcrypt + +bcrypt = Bcrypt() + +# 读取 JSON 数据 +with open('students_config.json', 'r', encoding='utf-8') as f: + students_config = json.load(f) + +# 连接到 MySQL 数据库 +conn = mysql.connector.connect( + host='8.218.165.242', + user='sure_001', + password='EKKWLMmrGmG7sdPf', + database='sure_001' +) + +cursor = conn.cursor() + +# 创建 students 表 +cursor.execute(''' +CREATE TABLE IF NOT EXISTS students ( + id VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL +) +''') + +# 插入数据并哈希默认密码 +default_password_hash = bcrypt.generate_password_hash('skd123456').decode('utf-8') +for student in students_config['students']: + cursor.execute('INSERT INTO students (id, name, email, password) VALUES (%s, %s, %s, %s)', + (student['id'], student['name'], student['email'], default_password_hash)) + +conn.commit() +cursor.close() +conn.close() diff --git a/main.py b/main.py deleted file mode 100644 index 5b25c56..0000000 --- a/main.py +++ /dev/null @@ -1,16 +0,0 @@ -# 这是一个示例 Python 脚本。 - -# 按 ⌃R 执行或将其替换为您的代码。 -# 按 双击 ⇧ 在所有地方搜索类、文件、工具窗口、操作和设置。 - - -def print_hi(name): - # 在下面的代码行中使用断点来调试脚本。 - print(f'Hi, {name}') # 按 ⌘F8 切换断点。 - - -# 按间距中的绿色按钮以运行脚本。 -if __name__ == '__main__': - print_hi('PyCharm') - -# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助 diff --git a/output_files_content.docx b/output_files_content.docx new file mode 100644 index 0000000..ef13144 Binary files /dev/null and b/output_files_content.docx differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a4df60b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +Flask +flask-cors +flask-mail +Flask-Bcrypt +Flask-Session +mysql-connector-python +boto3 +botocore +python-dotenv +pytz +pandas +docx~=0.2.4 \ No newline at end of file diff --git a/scp_to_remote_host.txt b/scp_to_remote_host.txt new file mode 100644 index 0000000..958bf19 --- /dev/null +++ b/scp_to_remote_host.txt @@ -0,0 +1 @@ +scp -r /Users/lishunqin/Desktop/study/pychram\ project/AWS-sure root@8.218.165.242:/root \ No newline at end of file diff --git a/static/admin.css b/static/admin.css new file mode 100644 index 0000000..56d0782 --- /dev/null +++ b/static/admin.css @@ -0,0 +1,99 @@ +body { + font-family: Arial, sans-serif; + line-height: 1.6; + margin: 0; + padding: 0; + background-color: #f4f4f4; +} + +.container { + width: 80%; + margin: auto; + overflow: hidden; + padding: 20px; +} + +h1, h2 { + color: #333; +} + +.form-group { + margin-bottom: 15px; +} + +label { + display: block; + margin-bottom: 5px; +} + +input[type="text"], +input[type="password"], +input[type="date"] { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; +} + +button { + display: inline-block; + background: #333; + color: #fff; + padding: 10px 20px; + border: none; + cursor: pointer; +} + +button:hover { + background: #555; +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +table, th, td { + border: 1px solid #ddd; +} + +th, td { + text-align: left; + padding: 10px; +} + +th { + background-color: #f4f4f4; +} + +.alert { + padding: 10px; + margin-bottom: 15px; + border-radius: 4px; +} + +.alert-success { + background-color: #d4edda; + border-color: #c3e6cb; + color: #155724; +} + +.alert-error { + background-color: #f8d7da; + border-color: #f5c6cb; + color: #721c24; +} + +.logout-btn { + float: right; + background: #dc3545; + color: #fff; + padding: 5px 10px; + text-decoration: none; + border-radius: 4px; +} + +.logout-btn:hover { + background: #c82333; +} diff --git a/static/base.css b/static/base.css new file mode 100644 index 0000000..30fed4e --- /dev/null +++ b/static/base.css @@ -0,0 +1,119 @@ +/* base.css */ + +body { + font-family: Arial, sans-serif; + line-height: 1.6; + margin: 0; + padding: 0; + background-color: #f4f4f4; + color: #333; +} + +.container { + width: 80%; + max-width: 800px; + margin: 50px auto; + background: #fff; + padding: 20px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + border-radius: 8px; +} + +h1, h2 { + color: #111; + margin-bottom: 20px; +} + +.form-group { + margin-bottom: 15px; +} + +label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="text"], +input[type="email"], +input[type="password"], +input[type="number"], +input[type="date"], +select { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; +} + +button { + background: #5cb85c; + color: #fff; + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: background 0.3s, transform 0.3s; +} + +button:hover { + background: #4cae4c; + transform: translateY(-2px); +} + +.alert { + padding: 10px; + margin-bottom: 15px; + border-radius: 4px; +} + +.alert-success { + background-color: #d4edda; + border-color: #c3e6cb; + color: #155724; +} + +.alert-error { + background-color: #f8d7da; + border-color: #f5c6cb; + color: #721c24; +} + +.logout-btn { + float: right; + background: #dc3545; + color: #fff; + padding: 8px 16px; + text-decoration: none; + border-radius: 4px; + display: inline-block; +} + +.logout-btn:hover { + background: #c82333; +} + +table { + width: 100%; + margin-top: 20px; + border-collapse: collapse; +} + +th, td { + padding: 10px; + border-bottom: 1px solid #ddd; + text-align: left; +} + +th { + background-color: #f4f4f4; +} + +select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: url('data:image/svg+xml;utf8, { + if (response.ok) { + alert('截止日期已更新'); + location.reload(); + } else { + alert('更新失败'); + } + }) + .catch(error => console.error('Error:', error)); + } +} + +function deleteAssignment(assignmentId) { + if (confirm("您确定要删除这个作业吗?")) { + fetch(`/teacher/delete_assignment/${assignmentId}`, { + method: 'DELETE' + }) + .then(response => { + if (response.ok) { + alert('作业已删除'); + location.reload(); + } else { + alert('删除失败'); + } + }) + .catch(error => console.error('Error:', error)); + } +} diff --git a/static/dogking.jpg b/static/dogking.jpg new file mode 100644 index 0000000..a55b743 Binary files /dev/null and b/static/dogking.jpg differ diff --git a/image.jpg b/static/image.jpg similarity index 100% rename from image.jpg rename to static/image.jpg diff --git a/static/index.css b/static/index.css new file mode 100644 index 0000000..2447fdd --- /dev/null +++ b/static/index.css @@ -0,0 +1,312 @@ +:root { + --primary-color: #1890ff; + --secondary-color: #40a9ff; + --background-color: #f0f2f5; + --text-color: #333; + --border-color: #ddd; +} + +body { + font-family: 'Helvetica Neue', Arial, sans-serif; + background-color: var(--background-color); + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} + +.container { + background-color: #ffffff; + border-radius: 12px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + padding: 40px; + width: 100%; + max-width: 480px; + text-align: center; + transition: transform 0.3s ease; +} + +.container:hover { + transform: translateY(-5px); +} + +.image-container { + margin-bottom: 30px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.image-container img { + width: 100%; + height: auto; + display: block; + transition: transform 0.3s ease; +} + +.loading-indicator { + display: none; + margin-top: 15px; + font-size: 16px; + color: #666; + text-align: center; +} + +.image-container:hover img { + transform: scale(1.05); +} + +h1 { + color: var(--text-color); + font-size: 28px; + margin-bottom: 30px; + font-weight: 600; +} + +.form-field { + margin-bottom: 25px; + text-align: left; +} + +label { + display: block; + margin-bottom: 8px; + color: var(--text-color); + font-weight: 500; + font-size: 14px; +} + +select { + width: 100%; + padding: 12px; + border: 2px solid var(--border-color); + border-radius: 6px; + font-size: 16px; + transition: border-color 0.3s ease; +} + +select:focus { + outline: none; + border-color: var(--primary-color); +} + +.file-input-wrapper { + position: relative; + overflow: visible; + display: inline-block; + width: 100%; +} + +.file-input-button { + background-color: #f0f0f0; + border: 2px solid var(--border-color); + color: var(--text-color); + padding: 12px 15px; + border-radius: 6px; + font-size: 16px; + cursor: pointer; + display: inline-block; + width: 100%; + text-align: center; + box-sizing: border-box; + transition: all 0.3s ease; +} + +.file-input-button:hover { + background-color: #e0e0e0; + border-color: var(--primary-color); +} + +.file-input-button input[type="file"] { + display: none; +} + +#file-name { + margin-top: 8px; + font-size: 14px; + color: #666; +} + +button[type="submit"], button[id="verify-submit-button"] { + background-color: var(--primary-color); + color: white; + border: none; + padding: 14px 20px; + border-radius: 6px; + font-size: 16px; + cursor: pointer; + width: 100%; + transition: background-color 0.3s ease, transform 0.3s ease; + margin-top: 25px; + font-weight: 500; +} + +button[type="submit"]:hover, button[id="verify-submit-button"]:hover { + background-color: var(--secondary-color); + transform: translateY(-2px); +} + +button[type="submit"]:disabled, button[id="verify-submit-button"]:disabled { + background-color: #d3d3d3; + cursor: not-allowed; +} + +.progress-container { + margin-top: 25px; + display: none; +} + +.progress { + height: 12px; + background-color: #f0f0f0; + border-radius: 6px; + overflow: hidden; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.progress-bar { + height: 100%; + background-color: var(--primary-color); + width: 0; + transition: width 0.3s ease; +} + +#progress-percentage { + font-size: 14px; + color: #666; + margin-top: 8px; + font-weight: 500; +} + +#status { + margin-top: 20px; + font-size: 16px; + color: #666; + font-weight: 500; +} + +.upload-stats { + margin-top: 15px; + font-size: 14px; + color: #666; + display: flex; + justify-content: space-between; +} + +.upload-stats span { + display: inline-block; +} + +.button-group { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 20px; + margin-top: 30px; +} + +.action-card { + background: white; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: all 0.3s ease; +} + +.action-card:hover { + transform: translateY(-5px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} + +.verification-container { + margin-top: 25px; +} + +.verification-input { + width: 100%; + padding: 12px; + border: 2px solid var(--border-color); + border-radius: 6px; + font-size: 16px; + text-align: center; + margin-bottom: 15px; + transition: border-color 0.3s ease; +} + +.verification-input:focus { + outline: none; + border-color: var(--primary-color); +} + +.download-link-button, .preview-button { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + width: 100%; + border: none; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 500; + font-size: 14px; + color: white; + background-size: 200% 200%; + animation: rainbow 5s ease infinite; +} + +.download-link-button { + background-image: linear-gradient(45deg, #ff9a9e, #fad0c4, #ffecd2, #fcb69f); +} + +.preview-button { + background-image: linear-gradient(45deg, #a1c4fd, #c2e9fb, #d4fc79, #96e6a1); +} + +.download-link-button:hover, .preview-button:hover { + background-size: 100% 100%; +} + +.logout-button:hover { + background-color: var(--secondary-color); + transform: translateY(-2px); +} + +.logout-button { + background-color: var(--primary-color); + color: white; + border: none; + padding: 14px 20px; + border-radius: 6px; + font-size: 16px; + cursor: pointer; + width: 100%; + transition: background-color 0.3s ease, transform 0.3s ease; + margin-bottom: 25px; /* 适当的间距以与表单分隔开 */ + font-weight: 500; +} + +.button-icon { + font-size: 24px; + margin-bottom: 10px; +} + +@keyframes rainbow { + 0% { background-position: 0% 50% } + 50% { background-position: 100% 50% } + 100% { background-position: 0% 50% } +} + +@media (max-width: 600px) { + .container { + padding: 30px; + max-width: 90%; + } +} + +@media (max-width: 480px) { + .button-group { + grid-template-columns: 1fr; + } +} diff --git a/static/index.js b/static/index.js new file mode 100644 index 0000000..89346e8 --- /dev/null +++ b/static/index.js @@ -0,0 +1,275 @@ +document.querySelector('.file-input-button').addEventListener('click', function() { + document.getElementById('file').click(); +}); + +document.getElementById('file').addEventListener('change', function(e) { + const fileName = e.target.files[0] ? e.target.files[0].name : '未选择文件'; + document.getElementById('file-name').textContent = fileName; +}); + +document.getElementById('upload-form').addEventListener('submit', async (e) => { + e.preventDefault(); + handleSubmission(); +}); + +document.getElementById('verify-submit-button').addEventListener('click', async () => { + await validateCodeAndSubmit(); +}); + +async function handleSubmission() { + const assignment = document.getElementById('assignment').value; + const file = document.getElementById('file').files[0]; + const statusDiv = document.getElementById('status'); + + if (!assignment || !file) { + alert('请选择作业,并选择一个文件。'); + return; + } + + setLoadingState(true); + + try { + const recordResponse = await fetch('/record-submission', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ assignment, filename: file.name }) + }); + + if (recordResponse.status === 401) { + setLoadingState(false); + document.getElementById('verification-container').style.display = 'block'; + + const emailResponse = await fetch('/generate-code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ assignment }) + }); + + if (emailResponse.ok) { + alert('作业已提交过,请输入验证码提交'); + } else { + const errorData = await emailResponse.json(); + throw new Error(`发送验证码失败: ${errorData.error}`); + } + } else if (!recordResponse.ok) { + const errorRecordData = await recordResponse.json(); + throw new Error(`记录提交信息失败: ${errorRecordData.error}`); + } else { + const data = await recordResponse.json(); + await uploadFile(data.upload_url, file, statusDiv); + alert('文件上传并记录成功'); + } + } catch (error) { + console.error('错误:', error); + statusDiv.textContent = `错误: ${error.message}`; + alert('发生错误: ' + error.message); + } finally { + setLoadingState(false); + } +} + +async function validateCodeAndSubmit() { + const assignment = document.getElementById('assignment').value; + const file = document.getElementById('file').files[0]; + const code = document.getElementById('verification-code').value; + const statusDiv = document.getElementById('status'); + + setLoadingState(true, true); + + try { + const validateResponse = await fetch('/validate-code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ assignment, code }) + }); + + if (validateResponse.ok) { + const { upload_url } = await validateResponse.json(); + await uploadFile(upload_url, file, statusDiv); + alert('文件上传并记录成功'); + document.getElementById('verification-container').style.display = 'none'; + } else { + const errorData = await validateResponse.json(); + throw new Error(`验证码错误: ${errorData.error}`); + } + } catch (error) { + console.error('错误:', error); + statusDiv.textContent = `错误: ${error.message}`; + alert('发生错误: ' + error.message); + } finally { + setLoadingState(false, true); + } +} + +async function uploadFile(upload_url, file, statusDiv) { + const progressContainer = document.getElementById('progress-container'); + const progressBar = document.getElementById('progress-bar'); + const progressPercentage = document.getElementById('progress-percentage'); + const uploadSpeed = document.getElementById('upload-speed'); + const uploadSize = document.getElementById('upload-size'); + const uploadTime = document.getElementById('upload-time'); + + statusDiv.textContent = '正在上传文件...'; + const totalSize = file.size; + let uploadedSize = 0; + let startTime = Date.now(); + + progressContainer.style.display = 'block'; + + const updateProgress = (additionalProgress = 0) => { + uploadedSize += additionalProgress; + const percentComplete = (uploadedSize / totalSize) * 100; + const elapsedTime = (Date.now() - startTime) / 1000; + const speed = uploadedSize / elapsedTime; + const remainingSize = totalSize - uploadedSize; + const estimatedRemainingTime = speed > 0 ? remainingSize / speed : 0; + + progressBar.style.width = percentComplete + '%'; + progressPercentage.textContent = percentComplete.toFixed(2) + '%'; + uploadSpeed.textContent = `速度: ${formatSize(speed)}/s`; + uploadSize.textContent = `${formatSize(uploadedSize)} / ${formatSize(totalSize)}`; + uploadTime.textContent = `剩余时间: ${formatTime(estimatedRemainingTime)}`; + }; + + const progressInterval = setInterval(() => { + if (uploadedSize < totalSize) { + updateProgress(totalSize / 100); + } + }, 200); + + const uploadResponse = await fetch(upload_url, { + method: 'PUT', + body: file, + headers: { + 'Content-Type': 'application/octet-stream' + } + }); + + clearInterval(progressInterval); + + if (!uploadResponse.ok) { + throw new Error(`上传失败: ${uploadResponse.statusText}`); + } + + updateProgress(totalSize - uploadedSize); +} + +function setLoadingState(isLoading, isVerify = false) { + const submitButton = document.querySelector('button[type="submit"]'); + const verifyButton = document.getElementById('verify-submit-button'); + const container = isVerify ? verifyButton.parentElement : submitButton.parentElement; + + const loadingIndicator = container.querySelector('.loading-indicator') || document.createElement('div'); + loadingIndicator.className = 'loading-indicator'; + loadingIndicator.innerHTML = '加载中...'; + loadingIndicator.style.display = isLoading ? 'block' : 'none'; + + if (!loadingIndicator.parentElement) { + container.appendChild(loadingIndicator); + } + + if (isVerify) { + verifyButton.disabled = isLoading; + } else { + submitButton.disabled = isLoading; + } +} + +function formatSize(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +function formatTime(seconds) { + if (isNaN(seconds) || !isFinite(seconds)) { + return '计算中'; + } + if (seconds < 60) return Math.round(seconds) + ' 秒'; + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.round(seconds % 60); + return `${minutes} 分 ${remainingSeconds} 秒`; +} + +function previewTable(apiUrl, title) { + fetch(apiUrl) + .then(response => response.json()) + .then(data => { + const newWindow = window.open('', '_blank'); + newWindow.document.write('' + title + ''); + newWindow.document.write('

' + title + '

'); + + const table = newWindow.document.createElement('table'); + + if (data.length > 0) { + let headers; + if (title === '预览统计表格') { + headers = ['时间', '提交的文件', '姓名', '学号', '作业']; + } else { + headers = ['学号', '姓名', '作业', '提交情况', '提交的文件']; + } + + const thead = newWindow.document.createElement('thead'); + const tr = newWindow.document.createElement('tr'); + headers.forEach((header, index) => { + const th = newWindow.document.createElement('th'); + th.textContent = header; + // 设置列宽 + if (title === '预览统计表格') { + if (index === 0) th.style.width = '20%'; // 时间 + else if (index === 1) th.style.width = '30%'; // 提交的文件 + else if (index === 2) th.style.width = '15%'; // 姓名 + else if (index === 3) th.style.width = '15%'; // 学号 + else if (index === 4) th.style.width = '20%'; // 作业 + } else { + if (index === 0) th.style.width = '15%'; // 学号 + else if (index === 1) th.style.width = '15%'; // 姓名 + else if (index === 2) th.style.width = '20%'; // 作业 + else if (index === 3) th.style.width = '15%'; // 提交情况 + else if (index === 4) th.style.width = '35%'; // 提交的文件 + } + tr.appendChild(th); + }); + thead.appendChild(tr); + table.appendChild(thead); + } + + const tbody = newWindow.document.createElement('tbody'); + data.forEach(row => { + const tr = newWindow.document.createElement('tr'); + if (title === '预览统计表格') { + ['时间', '提交的文件', '姓名', '学号', '作业'].forEach(key => { + const td = newWindow.document.createElement('td'); + td.textContent = row[key] || ''; + tr.appendChild(td); + }); + } else { + ['学号', '姓名', '作业', '提交情况', '提交的文件'].forEach(key => { + const td = newWindow.document.createElement('td'); + td.textContent = row[key] || ''; + tr.appendChild(td); + }); + } + tbody.appendChild(tr); + }); + + table.appendChild(tbody); + newWindow.document.body.appendChild(table); + newWindow.document.write(''); + newWindow.document.close(); + }) + .catch(error => console.error('Error fetching data:', error)); +} diff --git a/submissions.csv b/submissions.csv deleted file mode 100644 index 4c6ee6f..0000000 --- a/submissions.csv +++ /dev/null @@ -1,20 +0,0 @@ -ID,学生姓名,学号,提交的文件 -2024-07-02T15:38:06.844568,嗷呜呜汪汪汪钦,12345,fastgpt.txt -2024-07-03T02:01:37.364487,王中王火腿肠,123,mao.png -2024-07-03T02:02:02.087326,王中王火腿肠,123,《移动互联开发》软件设计考查材料.rar -2024-07-03T02:31:23.019179,秀儿,000,SUS 304 Stainless Steel.png -2024-07-03T02:31:55.184382,秀儿,000,阅读1.pdf -2024-07-03T02:35:50.030507,嘿!,324,计算机网络_数据链路层.ppt -2024-07-03T02:37:23.232007,CC,34534,"性学观止 (贺兰特·凯查杜里安, 胡颖翀) (Z-Library).pdf" -2024-07-03T02:40:42.161975,4324,4234,数学分析 陈纪修 第三版 上 by 陈纪修,于崇华,金路 (z-lib.org).pdf -2024-07-03T02:42:47.614487,324,4234,男人来自火星,女人来自金星(套装共4册) (约翰·格雷) (Z-Library).pdf -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 deleted file mode 100644 index e3a2659..0000000 Binary files a/submissions.xlsx and /dev/null differ diff --git a/templates/admin_login.html b/templates/admin_login.html new file mode 100644 index 0000000..f35e05c --- /dev/null +++ b/templates/admin_login.html @@ -0,0 +1,33 @@ + + + + + + Admin Login + + + + +
+

Admin Login

+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} +
+
+ + +
+
+ + +
+ +
+
+ + \ No newline at end of file diff --git a/templates/admin_panel.html b/templates/admin_panel.html new file mode 100644 index 0000000..88b96ef --- /dev/null +++ b/templates/admin_panel.html @@ -0,0 +1,160 @@ + + + + + + 管理面板 + + + + +
+

管理面板

+ 登出 + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +

添加新专业

+
+
+ + +
+ +
+ +

添加新年级

+
+
+ + +
+ +
+ +

添加新班级

+
+
+ + +
+
+ + +
+
+ + +
+ +
+ +

分配教师到班级

+
+
+ + +
+
+ + +
+ +
+ +

添加新教师

+
+
+ + +
+
+ + +
+
+ + +
+ +
+ +

添加新管理员

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + + + + + diff --git a/templates/class_detail.html b/templates/class_detail.html new file mode 100644 index 0000000..80f97bf --- /dev/null +++ b/templates/class_detail.html @@ -0,0 +1,79 @@ + + + + + + 班级详情 + + + +
+

班级详情

+ 返回教师面板 + +

作业

+
+
+ + +
+
+ + +
+
+ + +
+ +
+ +

现有作业

+ + + + + + + + + + + {% for assignment in assignments %} + + + + + + + {% endfor %} + +
分值名称截止日期操作
{{ assignment.value }}{{ assignment.name }}{{ assignment.deadline }} + + +
+ +

学生

+ + + + + + + + + + {% for student in students %} + + + + + + {% endfor %} + +
学生 ID学生姓名邮箱
{{ student.id }}{{ student.name }}{{ student.email }}
+
+ + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..ed94bfc --- /dev/null +++ b/templates/index.html @@ -0,0 +1,86 @@ + + + + + + 秀儿文件提交系统 + + + +
+
+ 描述性文本 +
+ + +

秀儿文件提交系统

+
+
+ + +
+
+ +
+ +
+
未选择文件
+
+ +
+
+
+
+
+
+
0%
+
+ 速度: 0 KB/s + 0 KB / 0 KB + 剩余时间: 计算中 +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..91d94f8 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,121 @@ + + + + + + 登录页面 + + + +
+
+ Dog King +
+

登录

+ {% if error %} +
{{ error }}
+ {% endif %} +
+
+ + +
+
+ + +
+ + +
+
+ + diff --git a/templates/reset_password.html b/templates/reset_password.html new file mode 100644 index 0000000..3fd7bce --- /dev/null +++ b/templates/reset_password.html @@ -0,0 +1,131 @@ + + + + + + 重置密码 + + + +
+

重置密码

+ {% if error %} +
{{ error }}
+ {% endif %} + {% if success %} +
{{ success }}
+ {% endif %} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+ + diff --git a/templates/teacher_login.html b/templates/teacher_login.html new file mode 100644 index 0000000..7712451 --- /dev/null +++ b/templates/teacher_login.html @@ -0,0 +1,34 @@ + + + + + + Teacher Login + + + + +
+

Teacher Login

+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ + +
+
+ + +
+ +
+
+ + diff --git a/templates/teacher_panel.html b/templates/teacher_panel.html new file mode 100644 index 0000000..3accd11 --- /dev/null +++ b/templates/teacher_panel.html @@ -0,0 +1,40 @@ + + + + + + 教师面板 + + + + +
+

教师面板

+ 登出 + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +

选择班级

+ + + +
+ + + diff --git a/~$tput_files_content.docx b/~$tput_files_content.docx new file mode 100644 index 0000000..1215360 Binary files /dev/null and b/~$tput_files_content.docx differ