1-version upload system

This commit is contained in:
superlishunqin 2024-08-29 02:06:09 +08:00
parent 6129cd506d
commit cc4f4c253c
88 changed files with 2818 additions and 1187 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

22
.env
View File

@ -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

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
app.py

2
.idea/AWS-sure.iml generated
View File

@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv/lib/python3.10/site-packages" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

2
.idea/misc.xml generated
View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (AWS-sure)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
</project>

Binary file not shown.

247
app.py
View File

@ -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)

223
app.txt
View File

@ -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)

781
app_function.py Normal file
View File

@ -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/<int:class_id>')
@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/<int:class_id>/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/<int:assignment_id>', 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/<int:assignment_id>', 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'})

69
ccc.py Normal file
View File

@ -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}")

280
database.py Normal file
View File

@ -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)

46
edit_admin_info.py Normal file
View File

@ -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.")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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)

2
generate_secret_key.py Normal file
View File

@ -0,0 +1,2 @@
import secrets
print(secrets.token_hex(16)) # 生成一个32字符长的随机字符串

View File

@ -1,342 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>秀儿文件提交系统</title>
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 30px;
width: 100%;
max-width: 400px;
}
.image-container {
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
}
.image-container img {
width: 100%;
height: auto;
display: block;
}
h1 {
color: #333;
font-size: 24px;
margin-bottom: 20px;
text-align: center;
}
.form-field {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: 500;
}
input[type="text"], select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.file-input-wrapper {
position: relative;
overflow: visible;
display: inline-block;
width: 100%;
}
.file-input-button {
background-color: #f0f0f0;
border: 1px solid #ddd;
color: #333;
padding: 10px 15px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
display: inline-block;
width: 100%;
text-align: center;
box-sizing: border-box;
transition: background-color 0.3s, border-color 0.3s;
}
.file-input-button:hover {
background-color: #e0e0e0;
border-color: #ccc;
}
.file-input-button input[type=file] {
display: none;
}
#file-name {
margin-top: 5px;
font-size: 14px;
color: #666;
}
button[type="submit"] {
background-color: #1890ff;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
width: 100%;
transition: background-color 0.3s;
}
button[type="submit"]:hover {
background-color: #40a9ff;
}
.progress-container {
margin-top: 20px;
display: none;
}
.progress {
height: 10px;
background-color: #f0f0f0;
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #1890ff;
width: 0;
transition: width 0.3s ease;
}
#progress-percentage {
font-size: 14px;
color: #666;
margin-top: 5px;
}
#status {
margin-top: 15px;
font-size: 14px;
color: #666;
}
#download-link {
display: inline-block;
margin-top: 20px;
text-decoration: none;
color: #1890ff;
font-size: 14px;
}
.upload-stats {
margin-top: 10px;
font-size: 14px;
color: #666;
}
.upload-stats span {
display: inline-block;
margin-right: 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="image-container">
<img src="image.jpg" alt="描述性文本">
</div>
<h1>秀儿文件提交系统</h1>
<form id="upload-form">
<div class="form-field">
<label for="student">学生姓名:</label>
<input type="text" id="student" name="student" required>
</div>
<div class="form-field">
<label for="student-id">学号:</label>
<input type="text" id="student-id" name="student-id" required>
</div>
<div class="form-field">
<label for="assignment">选择作业:</label>
<select id="assignment" name="assignment" required>
<option value="">请选择作业</option>
<option value="homework1">平时作业1</option>
<option value="homework2">平时作业2</option>
<option value="homework3">平时作业3</option>
<option value="final_homework">结课大作业</option>
</select>
</div>
<div class="form-field">
<label for="file">选择文件:</label>
<div class="file-input-wrapper">
<button type="button" class="file-input-button">选择文件 <input type="file" id="file" name="file" required></button>
</div>
<div id="file-name">未选择文件</div>
</div>
<button type="submit">提交 (截止日期: 2024.07.03)</button>
</form>
<div id="status"></div>
<div class="progress-container" id="progress-container">
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div id="progress-percentage">0%</div>
<div class="upload-stats">
<span id="upload-speed">速度: 0 KB/s</span>
<span id="upload-size">0 KB / 0 KB</span>
<span id="upload-time">剩余时间: 计算中</span>
</div>
</div>
<a href="/download-submissions" id="download-link">下载统计表格</a>
</div>
<script>
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();
const student = document.getElementById('student').value;
const studentId = document.getElementById('student-id').value;
const assignment = document.getElementById('assignment').value;
const file = document.getElementById('file').files[0];
const statusDiv = document.getElementById('status');
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');
if (!student || !studentId || !assignment || !file) {
alert('请提供学生姓名、学号、选择作业,并选择一个文件。');
return;
}
statusDiv.textContent = '准备上传...';
progressContainer.style.display = 'block';
try {
const response = await fetch(`/generate-url?student=${encodeURIComponent(student)}&student_id=${encodeURIComponent(studentId)}&assignment=${encodeURIComponent(assignment)}&filename=${encodeURIComponent(file.name)}&content_type=${encodeURIComponent(file.type)}`, {
method: 'GET',
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`生成预签名URL失败: ${errorData.error}`);
}
const data = await response.json();
if (!data.url) {
throw new Error('服务器未返回URL');
}
statusDiv.textContent = '正在上传文件...';
const totalSize = file.size;
let uploadedSize = 0;
let startTime = Date.now();
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); // 每次更新1%的进度
}
}, 200);
// 实际上传
const uploadResponse = await fetch(data.url, {
method: 'PUT',
body: file,
headers: {
'Content-Type': data.content_type
}
});
clearInterval(progressInterval);
if (!uploadResponse.ok) {
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
}
// 确保进度达到100%
updateProgress(totalSize - uploadedSize);
statusDiv.textContent = '文件上传成功';
// 上传成功后,记录提交信息
const recordResponse = await fetch('/record-submission', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
student,
student_id: studentId,
assignment,
filename: file.name
})
});
if (!recordResponse.ok) {
const errorRecordData = await recordResponse.json();
throw new Error(`Failed to record submission: ${errorRecordData.error}`);
}
alert('文件上传并记录成功');
} catch (error) {
console.error('错误:', error);
statusDiv.textContent = `错误: ${error.message}`;
alert('发生错误: ' + error.message);
}
});
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} 秒`;
}
</script>
</body>
</html>

342
index.txt
View File

@ -1,342 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>秀儿文件提交系统</title>
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 30px;
width: 100%;
max-width: 400px;
}
.image-container {
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
}
.image-container img {
width: 100%;
height: auto;
display: block;
}
h1 {
color: #333;
font-size: 24px;
margin-bottom: 20px;
text-align: center;
}
.form-field {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: 500;
}
input[type="text"], select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.file-input-wrapper {
position: relative;
overflow: visible;
display: inline-block;
width: 100%;
}
.file-input-button {
background-color: #f0f0f0;
border: 1px solid #ddd;
color: #333;
padding: 10px 15px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
display: inline-block;
width: 100%;
text-align: center;
box-sizing: border-box;
transition: background-color 0.3s, border-color 0.3s;
}
.file-input-button:hover {
background-color: #e0e0e0;
border-color: #ccc;
}
.file-input-button input[type=file] {
display: none;
}
#file-name {
margin-top: 5px;
font-size: 14px;
color: #666;
}
button[type="submit"] {
background-color: #1890ff;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
width: 100%;
transition: background-color 0.3s;
}
button[type="submit"]:hover {
background-color: #40a9ff;
}
.progress-container {
margin-top: 20px;
display: none;
}
.progress {
height: 10px;
background-color: #f0f0f0;
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #1890ff;
width: 0;
transition: width 0.3s ease;
}
#progress-percentage {
font-size: 14px;
color: #666;
margin-top: 5px;
}
#status {
margin-top: 15px;
font-size: 14px;
color: #666;
}
#download-link {
display: inline-block;
margin-top: 20px;
text-decoration: none;
color: #1890ff;
font-size: 14px;
}
.upload-stats {
margin-top: 10px;
font-size: 14px;
color: #666;
}
.upload-stats span {
display: inline-block;
margin-right: 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="image-container">
<img src="image.jpg" alt="描述性文本">
</div>
<h1>秀儿文件提交系统</h1>
<form id="upload-form">
<div class="form-field">
<label for="student">学生姓名:</label>
<input type="text" id="student" name="student" required>
</div>
<div class="form-field">
<label for="student-id">学号:</label>
<input type="text" id="student-id" name="student-id" required>
</div>
<div class="form-field">
<label for="assignment">选择作业:</label>
<select id="assignment" name="assignment" required>
<option value="">请选择作业</option>
<option value="homework1">平时作业1</option>
<option value="homework2">平时作业2</option>
<option value="homework3">平时作业3</option>
<option value="final_homework">结课大作业</option>
</select>
</div>
<div class="form-field">
<label for="file">选择文件:</label>
<div class="file-input-wrapper">
<button type="button" class="file-input-button">选择文件 <input type="file" id="file" name="file" required></button>
</div>
<div id="file-name">未选择文件</div>
</div>
<button type="submit">提交 (截止日期: 2024.07.03)</button>
</form>
<div id="status"></div>
<div class="progress-container" id="progress-container">
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div id="progress-percentage">0%</div>
<div class="upload-stats">
<span id="upload-speed">速度: 0 KB/s</span>
<span id="upload-size">0 KB / 0 KB</span>
<span id="upload-time">剩余时间: 计算中</span>
</div>
</div>
<a href="/download-submissions" id="download-link">下载统计表格</a>
</div>
<script>
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();
const student = document.getElementById('student').value;
const studentId = document.getElementById('student-id').value;
const assignment = document.getElementById('assignment').value;
const file = document.getElementById('file').files[0];
const statusDiv = document.getElementById('status');
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');
if (!student || !studentId || !assignment || !file) {
alert('请提供学生姓名、学号、选择作业,并选择一个文件。');
return;
}
statusDiv.textContent = '准备上传...';
progressContainer.style.display = 'block';
try {
const response = await fetch(`/generate-url?student=${encodeURIComponent(student)}&student_id=${encodeURIComponent(studentId)}&assignment=${encodeURIComponent(assignment)}&filename=${encodeURIComponent(file.name)}&content_type=${encodeURIComponent(file.type)}`, {
method: 'GET',
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`生成预签名URL失败: ${errorData.error}`);
}
const data = await response.json();
if (!data.url) {
throw new Error('服务器未返回URL');
}
statusDiv.textContent = '正在上传文件...';
const totalSize = file.size;
let uploadedSize = 0;
let startTime = Date.now();
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); // 每次更新1%的进度
}
}, 200);
// 实际上传
const uploadResponse = await fetch(data.url, {
method: 'PUT',
body: file,
headers: {
'Content-Type': data.content_type
}
});
clearInterval(progressInterval);
if (!uploadResponse.ok) {
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
}
// 确保进度达到100%
updateProgress(totalSize - uploadedSize);
statusDiv.textContent = '文件上传成功';
// 上传成功后,记录提交信息
const recordResponse = await fetch('/record-submission', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
student,
student_id: studentId,
assignment,
filename: file.name
})
});
if (!recordResponse.ok) {
const errorRecordData = await recordResponse.json();
throw new Error(`Failed to record submission: ${errorRecordData.error}`);
}
alert('文件上传并记录成功');
} catch (error) {
console.error('错误:', error);
statusDiv.textContent = `错误: ${error.message}`;
alert('发生错误: ' + error.message);
}
});
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} 秒`;
}
</script>
</body>
</html>

View File

@ -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()

16
main.py
View File

@ -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 帮助

BIN
output_files_content.docx Normal file

Binary file not shown.

12
requirements.txt Normal file
View File

@ -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

1
scp_to_remote_host.txt Normal file
View File

@ -0,0 +1 @@
scp -r /Users/lishunqin/Desktop/study/pychram\ project/AWS-sure root@8.218.165.242:/root

99
static/admin.css Normal file
View File

@ -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;
}

119
static/base.css Normal file
View File

@ -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,<svg x...') no-repeat right .75rem center / 14px 12px;
}

38
static/class_detail.js Normal file
View File

@ -0,0 +1,38 @@
function editAssignment(assignmentId, currentDeadline) {
const newDeadline = prompt("请输入新的截止日期格式YYYY-MM-DD:", currentDeadline);
if (newDeadline) {
fetch(`/teacher/edit_assignment/${assignmentId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ deadline: newDeadline })
})
.then(response => {
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));
}
}

BIN
static/dogking.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

View File

Before

Width:  |  Height:  |  Size: 493 KiB

After

Width:  |  Height:  |  Size: 493 KiB

312
static/index.css Normal file
View File

@ -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;
}
}

275
static/index.js Normal file
View File

@ -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 = '<span>加载中...</span>';
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('<html><head><title>' + title + '</title><style>');
newWindow.document.write('table { width: 100%; border-collapse: collapse; margin-top: 10px; }');
newWindow.document.write('th, td { border: 1px solid #ddd; padding: 8px; text-align: left; word-wrap: break-word; }');
newWindow.document.write('th { background-color: #f2f2f2; }');
newWindow.document.write('body { font-family: Arial, sans-serif; padding: 20px; }');
newWindow.document.write('</style></head><body>');
newWindow.document.write('<h2>' + title + '</h2>');
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('</body></html>');
newWindow.document.close();
})
.catch(error => console.error('Error fetching data:', error));
}

View File

@ -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
Can't render this file because it has a wrong number of fields in line 14.

Binary file not shown.

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login</title>
<link rel="stylesheet" href="/static/admin.css">
<link rel="stylesheet" href="/static/base.css">
</head>
<body>
<div class="container">
<h1>Admin Login</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>

160
templates/admin_panel.html Normal file
View File

@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理面板</title>
<link rel="stylesheet" href="/static/admin.css">
<link rel="stylesheet" href="/static/base.css">
</head>
<body>
<div class="container">
<h1>管理面板</h1>
<a href="{{ url_for('admin_logout') }}" class="logout-btn">登出</a>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<h2>添加新专业</h2>
<form action="{{ url_for('add_department') }}" method="POST">
<div class="form-group">
<label for="department_name">专业名称:</label>
<input type="text" id="department_name" name="name" required>
</div>
<button type="submit">添加专业</button>
</form>
<h2>添加新年级</h2>
<form action="{{ url_for('add_grade') }}" method="POST">
<div class="form-group">
<label for="year">年份:</label>
<input type="number" id="year" name="year" required min="2000" max="2100">
</div>
<button type="submit">添加年级</button>
</form>
<h2>添加新班级</h2>
<form action="{{ url_for('add_class') }}" method="POST">
<div class="form-group">
<label for="class_name">班级名称:</label>
<input type="text" id="class_name" name="name" required>
</div>
<div class="form-group">
<label for="department_id">专业:</label>
<select id="department_id" name="department_id" required>
{% for department in departments %}
<option value="{{ department.id }}">{{ department.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="grade_id">年级:</label>
<select id="grade_id" name="grade_id" required>
{% for grade in grades %}
<option value="{{ grade.id }}">{{ grade.year }}</option>
{% endfor %}
</select>
</div>
<button type="submit">添加班级</button>
</form>
<h2>分配教师到班级</h2>
<form action="{{ url_for('assign_teacher') }}" method="POST">
<div class="form-group">
<label for="class_id">班级:</label>
<select id="class_id" name="class_id" required>
{% for class in classes %}
<option value="{{ class.id }}">{{ class.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="teacher_id">教师:</label>
<select id="teacher_id" name="teacher_id" required>
{% for teacher in teachers %}
<option value="{{ teacher.id }}">{{ teacher.name }}</option>
{% endfor %}
</select>
</div>
<button type="submit">分配教师</button>
</form>
<h2>添加新教师</h2>
<form action="{{ url_for('add_teacher') }}" method="POST">
<div class="form-group">
<label for="teacher_name">教师姓名:</label>
<input type="text" id="teacher_name" name="name" required>
</div>
<div class="form-group">
<label for="teacher_email">邮箱:</label>
<input type="email" id="teacher_email" name="email" required>
</div>
<div class="form-group">
<label for="teacher_password">密码:</label>
<input type="password" id="teacher_password" name="password" required>
</div>
<button type="submit">添加教师</button>
</form>
<h2>添加新管理员</h2>
<form action="{{ url_for('add_administrator') }}" method="POST">
<div class="form-group">
<label for="admin_username">用户名:</label>
<input type="text" id="admin_username" name="username" required>
</div>
<div class="form-group">
<label for="admin_password">密码:</label>
<input type="password" id="admin_password" name="password" required>
</div>
<div class="form-group">
<label for="admin_teacher_id">教师:</label>
<select id="admin_teacher_id" name="teacher_id" required>
{% for teacher in teachers %}
<option value="{{ teacher.id }}">{{ teacher.name }}</option>
{% endfor %}
</select>
</div>
<button type="submit">添加管理员</button>
</form>
</div>
<div id="editFormContainer" style="display: none;">
<form id="editForm" method="POST">
<input type="hidden" id="editId" name="id">
<div class="form-group">
<label for="editValue">值:</label>
<input type="text" id="editValue" name="value" required>
</div>
<div class="form-group">
<label for="editName">名称:</label>
<input type="text" id="editName" name="name" required>
</div>
<div class="form-group">
<label for="editDeadline">截止日期:</label>
<input type="date" id="editDeadline" name="deadline" required>
</div>
<button type="submit">更新任务</button>
<button type="button" onclick="hideEditForm()">取消</button>
</form>
</div>
<script>
function showEditForm(id, value, name, deadline) {
document.getElementById('editId').value = id;
document.getElementById('editValue').value = value;
document.getElementById('editName').value = name;
document.getElementById('editDeadline').value = deadline;
document.getElementById('editForm').action = `/admin/edit_assignment/${id}`;
document.getElementById('editFormContainer').style.display = 'block';
}
function hideEditForm() {
document.getElementById('editFormContainer').style.display = 'none';
}
</script>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>班级详情</title>
<link rel="stylesheet" href="/static/base.css">
</head>
<body>
<div class="container">
<h1>班级详情</h1>
<a href="{{ url_for('teacher_panel') }}">返回教师面板</a>
<h2>作业</h2>
<form action="{{ url_for('teacher_add_assignment', class_id=class_id) }}" method="POST">
<div class="form-group">
<label for="value">作业分值:</label>
<input type="text" id="value" name="value" required>
</div>
<div class="form-group">
<label for="name">作业名称:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="deadline">截止日期:</label>
<input type="date" id="deadline" name="deadline" required>
</div>
<button type="submit">添加作业</button>
</form>
<h2>现有作业</h2>
<table>
<thead>
<tr>
<th>分值</th>
<th>名称</th>
<th>截止日期</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for assignment in assignments %}
<tr>
<td>{{ assignment.value }}</td>
<td>{{ assignment.name }}</td>
<td>{{ assignment.deadline }}</td>
<td>
<button onclick="editAssignment({{ assignment.id }}, '{{ assignment.deadline }}')">修改</button>
<button onclick="deleteAssignment({{ assignment.id }})">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2>学生</h2>
<table>
<thead>
<tr>
<th>学生 ID</th>
<th>学生姓名</th>
<th>邮箱</th>
</tr>
</thead>
<tbody>
{% for student in students %}
<tr>
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ student.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script src="/static/class_detail.js"></script>
</body>
</html>

86
templates/index.html Normal file
View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>秀儿文件提交系统</title>
<link rel="stylesheet" href="/static/index.css">
</head>
<body>
<div class="container">
<div class="image-container">
<img src="/static/image.jpg" alt="描述性文本">
</div>
<!-- 增加退出按钮 -->
<button class="logout-button" onclick="window.location.href='/logout'">退出账号</button>
<h1>秀儿文件提交系统</h1>
<form id="upload-form">
<div class="form-field">
<label for="assignment">选择作业:</label>
<select id="assignment" name="assignment" required>
<option value="">请选择作业</option>
{% for assignment in assignments %}
<option value="{{ assignment.value }}">{{ assignment.name }} (截止日期: {{ assignment.deadline }})</option>
{% endfor %}
</select>
</div>
<div class="form-field">
<label for="file">选择文件:</label>
<div class="file-input-wrapper">
<button type="button" class="file-input-button">选择文件 <input type="file" id="file" name="file" required></button>
</div>
<div id="file-name">未选择文件</div>
</div>
<button type="submit">提交 (截止日期: 2024.07.03)</button>
</form>
<div id="status"></div>
<div class="progress-container" id="progress-container">
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div id="progress-percentage">0%</div>
<div class="upload-stats">
<span id="upload-speed">速度: 0 KB/s</span>
<span id="upload-size">0 KB / 0 KB</span>
<span id="upload-time">剩余时间: 计算中</span>
</div>
</div>
<!-- 验证码输入框和按钮 -->
<div id="verification-container" class="verification-container" style="display: none;">
<div class="form-field">
<label for="verification-code">输入验证码:</label>
<input type="text" id="verification-code" class="verification-input" placeholder="请输入验证码">
</div>
<button id="verify-submit-button">提交验证码</button>
</div>
<div class="button-group">
<div class="action-card">
<button class="download-link-button" onclick="window.location.href='/download-submissions'">
<span class="button-icon">📊</span>
下载统计表格
</button>
</div>
<div class="action-card">
<button class="download-link-button" onclick="window.location.href='/download-assignment-status'">
<span class="button-icon">📥</span>
下载作业提交情况
</button>
</div>
<div class="action-card">
<button class="preview-button" onclick="previewTable('/api/submissions', '预览统计表格')">
<span class="button-icon">👁️</span>
预览统计表格
</button>
</div>
<div class="action-card">
<button class="preview-button" onclick="previewTable('/api/assignment-status', '预览作业提交情况')">
<span class="button-icon">🔍</span>
预览作业提交情况
</button>
</div>
</div>
</div>
<script src="/static/index.js"></script>
</body>
</html>

121
templates/login.html Normal file
View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
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;
}
.image-container:hover img {
transform: scale(1.05);
}
h1 {
color: #333;
font-size: 28px;
margin-bottom: 30px;
font-weight: 600;
}
.form-field {
margin-bottom: 25px;
text-align: left;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 6px;
font-size: 16px;
transition: border-color 0.3s ease;
}
input[type="text"]:focus, input[type="password"]:focus {
outline: none;
border-color: #1890ff;
}
button[type="submit"], button[type="button"] {
background-color: #1890ff;
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[type="button"]:hover {
background-color: #40a9ff;
transform: translateY(-2px);
}
.error {
color: red;
margin-bottom: 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="image-container">
<img src="../static/dogking.jpg" alt="Dog King">
</div>
<h1>登录</h1>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
<form action="{{ url_for('login') }}" method="post">
<div class="form-field">
<label for="student_id">学号:</label>
<input type="text" id="student_id" name="student_id" required>
</div>
<div class="form-field">
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">登录</button>
<button type="button" onclick="location.href='{{ url_for('reset_password') }}'">重置密码</button>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>重置密码</title>
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
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);
}
h1 {
color: #333;
font-size: 28px;
margin-bottom: 30px;
font-weight: 600;
}
.form-field {
margin-bottom: 25px;
text-align: left;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
input[type="text"], input[type="email"], input[type="password"] {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 6px;
font-size: 16px;
transition: border-color 0.3s ease;
}
input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus {
outline: none;
border-color: #1890ff;
}
.error, .success {
margin-bottom: 15px;
}
.error {
color: red;
}
.success {
color: green;
}
button[type="submit"], button[type="button"] {
background-color: #1890ff;
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[type="button"]:hover {
background-color: #40a9ff;
transform: translateY(-2px);
}
</style>
</head>
<body>
<div class="container">
<h1>重置密码</h1>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
{% if success %}
<div class="success">{{ success }}</div>
{% endif %}
<form action="{{ url_for('reset_password') }}" method="post">
<div class="form-field">
<label for="student_id">学号:</label>
<input type="text" id="student_id" name="student_id" required>
</div>
<div class="form-field">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-field">
<label for="code">验证码:</label>
<input type="text" id="code" name="code">
</div>
<div class="form-field">
<label for="new_password">新密码:</label>
<input type="password" id="new_password" name="new_password">
</div>
<button type="submit" name="reset_password">验证并修改密码</button>
<button type="submit" name="send_code">发送验证码</button>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Teacher Login</title>
<link rel="stylesheet" href="/static/style.css"> <!-- 您可以在此处链接到您的全局样式表 -->
<link rel="stylesheet" href="/static/base.css">
</head>
<body>
<div class="container">
<h1>Teacher Login</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form action="{{ url_for('teacher_login') }}" method="POST">
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>教师面板</title>
<link rel="stylesheet" href="/static/teacher.css">
<link rel="stylesheet" href="/static/base.css">
</head>
<body>
<div class="container">
<h1>教师面板</h1>
<a href="{{ url_for('teacher_logout') }}" class="logout-btn">登出</a>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<h2>选择班级</h2>
<select id="class_select" onchange="navigateToClass(this.value)">
<option value="">选择一个班级</option>
{% for class in classes %}
<option value="{{ class.id }}">{{ class.name }}</option>
{% endfor %}
</select>
<!-- 额外内容如班级作业和学生提交将在这里显示。 -->
</div>
<script>
function navigateToClass(classId) {
if (classId) {
window.location.href = `/teacher/class/${classId}`;
}
}
</script>
</body>
</html>

BIN
~$tput_files_content.docx Normal file

Binary file not shown.