引言
在现代教育环境中,Web作业提交系统已成为学生和教师不可或缺的工具。它简化了作业分发、提交和批改流程,提高了效率。然而,部署和使用过程中常遇到作业提交失败或文件上传错误的问题。本文将详细分享一个开源的Web作业提交系统源码(基于Python Flask框架),并提供完整的部署教程。同时,我们将深入探讨学生和教师如何诊断和解决常见错误。整个系统设计注重易用性和可扩展性,适合中小型教育机构使用。
为什么选择这个系统?它是一个轻量级Web应用,支持用户认证、作业创建、文件上传、成绩反馈等功能。源码基于Flask(一个Python微框架),使用SQLite作为数据库,便于本地部署。相比复杂的系统如Moodle,这个自定义系统更易上手,且源码完全开源,可根据需求修改。接下来,我们将逐步展开:先分享源码,然后讲解部署,最后聚焦问题解决。
系统概述
这个Web作业提交系统是一个前后端分离的简单应用。前端使用HTML/CSS/JavaScript(集成在Flask模板中),后端处理业务逻辑。核心功能包括:
- 用户角色:学生(提交作业、查看反馈)和教师(创建作业、批改、管理)。
- 作业管理:教师创建作业(标题、描述、截止日期),学生查看并提交。
- 文件上传:支持PDF、DOCX等常见格式,限制文件大小(默认5MB)。
- 认证系统:使用Flask-Login进行会话管理,确保安全。
- 数据存储:SQLite数据库存储用户、作业和提交记录。
系统架构简单:Flask处理路由,SQLite存储数据,文件保存在服务器本地目录。优点是部署快速(无需复杂服务器),缺点是不适合高并发(可扩展到PostgreSQL)。
源码分享
以下是系统的完整源码。我们将它分为几个文件:app.py(主应用)、models.py(数据库模型)、templates/(HTML模板)和static/(CSS/JS)。假设项目目录为homework_system/。
1. 项目结构
homework_system/
├── app.py
├── models.py
├── requirements.txt
├── uploads/ # 上传文件目录,自动生成
├── templates/
│ ├── base.html
│ ├── login.html
│ ├── student_dashboard.html
│ ├── teacher_dashboard.html
│ ├── create_homework.html
│ ├── submit_homework.html
│ └── view_submissions.html
└── static/
└── style.css
2. requirements.txt(依赖)
创建一个requirements.txt文件,列出所需Python包:
Flask==2.3.3
Flask-SQLAlchemy==3.0.5
Flask-Login==0.6.3
Werkzeug==2.3.7
安装命令:pip install -r requirements.txt。
3. models.py(数据库模型)
使用Flask-SQLAlchemy定义用户、作业和提交模型。
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(20), nullable=False) # 'student' or 'teacher'
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
class Homework(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=False)
deadline = db.Column(db.DateTime, nullable=False)
teacher_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
class Submission(db.Model):
id = db.Column(db.Integer, primary_key=True)
student_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
homework_id = db.Column(db.Integer, db.ForeignKey('homework.id'), nullable=False)
file_path = db.Column(db.String(200), nullable=False)
grade = db.Column(db.String(10), nullable=True)
feedback = db.Column(db.Text, nullable=True)
submitted_at = db.Column(db.DateTime, default=db.func.now())
说明:User模型使用密码哈希确保安全。Homework和Submission关联用户和作业。file_path存储上传文件的相对路径。
4. app.py(主应用)
这是核心文件,包含路由、认证和上传逻辑。运行python app.py启动服务器(默认端口5000)。
from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from models import db, User, Homework, Submission
from werkzeug.utils import secure_filename
from datetime import datetime
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-change-this' # 生产环境用环境变量
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///homework.db'
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5MB限制
# 初始化
db.init_app(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# 路由:登录
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user)
return redirect(url_for('dashboard'))
flash('用户名或密码错误')
return render_template('login.html')
# 路由:登出
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
# 路由:仪表盘(根据角色)
@app.route('/dashboard')
@login_required
def dashboard():
if current_user.role == 'student':
homeworks = Homework.query.all()
submissions = Submission.query.filter_by(student_id=current_user.id).all()
return render_template('student_dashboard.html', homeworks=homeworks, submissions=submissions)
else:
homeworks = Homework.query.filter_by(teacher_id=current_user.id).all()
return render_template('teacher_dashboard.html', homeworks=homeworks)
# 路由:创建作业(教师)
@app.route('/create_homework', methods=['GET', 'POST'])
@login_required
def create_homework():
if current_user.role != 'teacher':
return redirect(url_for('dashboard'))
if request.method == 'POST':
title = request.form['title']
description = request.form['description']
deadline_str = request.form['deadline']
deadline = datetime.strptime(deadline_str, '%Y-%m-%dT%H:%M')
homework = Homework(title=title, description=description, deadline=deadline, teacher_id=current_user.id)
db.session.add(homework)
db.session.commit()
flash('作业创建成功')
return redirect(url_for('dashboard'))
return render_template('create_homework.html')
# 路由:提交作业(学生)
@app.route('/submit_homework/<int:homework_id>', methods=['GET', 'POST'])
@login_required
def submit_homework(homework_id):
if current_user.role != 'student':
return redirect(url_for('dashboard'))
homework = Homework.query.get_or_404(homework_id)
if request.method == 'POST':
if 'file' not in request.files:
flash('没有文件')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('未选择文件')
return redirect(request.url)
if file:
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], f"{current_user.id}_{homework_id}_{filename}")
file.save(file_path)
submission = Submission(student_id=current_user.id, homework_id=homework_id, file_path=file_path)
db.session.add(submission)
db.session.commit()
flash('提交成功')
return redirect(url_for('dashboard'))
return render_template('submit_homework.html', homework=homework)
# 路由:查看提交(教师)
@app.route('/view_submissions/<int:homework_id>')
@login_required
def view_submissions(homework_id):
if current_user.role != 'teacher':
return redirect(url_for('dashboard'))
homework = Homework.query.get_or_404(homework_id)
submissions = Submission.query.filter_by(homework_id=homework_id).all()
return render_template('view_submissions.html', homework=homework, submissions=submissions)
# 路由:批改(教师)
@app.route('/grade_submission/<int:submission_id>', methods=['POST'])
@login_required
def grade_submission(submission_id):
if current_user.role != 'teacher':
return redirect(url_for('dashboard'))
submission = Submission.query.get_or_404(submission_id)
submission.grade = request.form['grade']
submission.feedback = request.form['feedback']
db.session.commit()
flash('成绩已更新')
return redirect(url_for('view_submissions', homework_id=submission.homework_id))
# 路由:下载文件
@app.route('/download/<int:submission_id>')
@login_required
def download(submission_id):
submission = Submission.query.get_or_404(submission_id)
directory = os.path.dirname(submission.file_path)
filename = os.path.basename(submission.file_path)
return send_from_directory(directory, filename, as_attachment=True)
# 初始化数据库(首次运行)
@app.before_first_request
def create_tables():
db.create_all()
# 示例:创建默认教师用户(仅测试用,生产环境删除)
if not User.query.filter_by(username='teacher').first():
teacher = User(username='teacher', role='teacher')
teacher.set_password('teacher123')
db.session.add(teacher)
student = User(username='student', role='student')
student.set_password('student123')
db.session.add(student)
db.session.commit()
if __name__ == '__main__':
app.run(debug=True)
代码说明:
- 认证:使用
@login_required装饰器保护路由。登录后根据角色重定向。 - 文件上传:
secure_filename防止路径注入,MAX_CONTENT_LENGTH限制大小。文件名包含用户ID和作业ID,避免冲突。 - 错误处理:使用
flash显示消息(如“没有文件”)。 - 安全性:密码哈希、文件路径验证。生产环境需添加HTTPS和输入验证。
- 扩展:可添加邮件通知或前端AJAX上传。
5. 模板文件(templates/)
使用Jinja2模板。以下是关键模板的简化版(完整版可从GitHub克隆)。
base.html(基础模板)
<!DOCTYPE html>
<html>
<head>
<title>作业提交系统</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
{% if current_user.is_authenticated %}
<a href="{{ url_for('dashboard') }}">仪表盘</a>
<a href="{{ url_for('logout') }}">登出</a>
{% endif %}
</nav>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
login.html
{% extends "base.html" %}
{% block content %}
<h2>登录</h2>
<form method="POST">
<input type="text" name="username" placeholder="用户名" required><br>
<input type="password" name="password" placeholder="密码" required><br>
<button type="submit">登录</button>
</form>
<p>默认教师:teacher / teacher123;学生:student / student123</p>
{% endblock %}
student_dashboard.html
{% extends "base.html" %}
{% block content %}
<h2>学生仪表盘</h2>
<h3>可用作业</h3>
{% for hw in homeworks %}
<div>
<h4>{{ hw.title }}</h4>
<p>{{ hw.description }}</p>
<p>截止:{{ hw.deadline }}</p>
<a href="{{ url_for('submit_homework', homework_id=hw.id) }}">提交</a>
</div>
{% endfor %}
<h3>我的提交</h3>
{% for sub in submissions %}
<div>
<p>作业:{{ Homework.query.get(sub.homework_id).title }}</p>
<p>成绩:{{ sub.grade or '未批改' }}</p>
<p>反馈:{{ sub.feedback or '无' }}</p>
<a href="{{ url_for('download', submission_id=sub.id) }}">下载</a>
</div>
{% endfor %}
{% endblock %}
teacher_dashboard.html
{% extends "base.html" %}
{% block content %}
<h2>教师仪表盘</h2>
<a href="{{ url_for('create_homework') }}">创建作业</a>
{% for hw in homeworks %}
<div>
<h4>{{ hw.title }}</h4>
<a href="{{ url_for('view_submissions', homework_id=hw.id) }}">查看提交</a>
</div>
{% endfor %}
{% endblock %}
submit_homework.html
{% extends "base.html" %}
{% block content %}
<h2>提交作业:{{ homework.title }}</h2>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" required><br>
<button type="submit">提交</button>
</form>
{% endblock %}
view_submissions.html
{% extends "base.html" %}
{% block content %}
<h2>作业提交:{{ homework.title }}</h2>
{% for sub in submissions %}
<div>
<p>学生:{{ User.query.get(sub.student_id).username }}</p>
<p>提交时间:{{ sub.submitted_at }}</p>
<a href="{{ url_for('download', submission_id=sub.id) }}">下载文件</a>
<form method="POST" action="{{ url_for('grade_submission', submission_id=sub.id) }}">
<input type="text" name="grade" placeholder="成绩" value="{{ sub.grade or '' }}">
<textarea name="feedback" placeholder="反馈">{{ sub.feedback or '' }}</textarea>
<button type="submit">保存</button>
</form>
</div>
{% endfor %}
{% endblock %}
static/style.css(简单样式)
body { font-family: Arial; margin: 20px; }
nav a { margin-right: 15px; }
.flashes { color: red; list-style: none; }
input, textarea, button { margin: 5px 0; display: block; width: 100%; max-width: 300px; }
div { border: 1px solid #ccc; padding: 10px; margin: 10px 0; }
源码获取:以上代码可直接复制使用。完整项目建议上传到GitHub,便于分享。测试时,运行python app.py,访问http://127.0.0.1:5000。
部署教程
部署这个系统非常简单,适合本地或云服务器(如阿里云、AWS)。我们以Ubuntu服务器为例,逐步指导。整个过程约30分钟。
步骤1:环境准备
安装Python:确保服务器有Python 3.8+。
sudo apt update sudo apt install python3 python3-pip python3-venv创建虚拟环境(推荐,避免依赖冲突)。
mkdir homework_system cd homework_system python3 -m venv venv source venv/bin/activate上传源码:将上述文件复制到服务器(使用SCP或Git)。
# 示例:从本地上传 scp -r /local/path/to/homework_system user@server_ip:/home/user/
步骤2:安装依赖
在项目目录下:
pip install -r requirements.txt
如果使用虚拟环境,确保激活:source venv/bin/activate。
步骤3:初始化数据库
运行应用一次创建数据库:
python app.py
# 按Ctrl+C停止(它会创建homework.db和uploads目录)
这会生成SQLite数据库文件。首次登录使用默认用户(teacher/teacher123)。
步骤4:运行应用
- 开发模式(测试):
python app.py,访问http://服务器IP:5000。 - 生产模式:使用Gunicorn(WSGI服务器)。
这会在端口5000运行4个工作进程。pip install gunicorn gunicorn -w 4 -b 0.0.0.0:5000 app:app
步骤5:配置Nginx和HTTPS(生产推荐)
安装Nginx:
sudo apt install nginx配置Nginx(编辑
/etc/nginx/sites-available/default):server { listen 80; server_name yourdomain.com; # 替换为域名或IP location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /static/ { alias /home/user/homework_system/static/; } location /uploads/ { alias /home/user/homework_system/uploads/; # 限制访问,仅认证用户可下载 auth_request /auth; # 需额外实现认证代理 } }重启Nginx:
sudo systemctl restart nginx。HTTPS:使用Let’s Encrypt免费证书。
sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.com这会自动配置SSL。
步骤6:安全优化
- 环境变量:将
SECRET_KEY和数据库路径移到.env文件,使用python-dotenv加载。 - 防火墙:
sudo ufw allow 80,443。 - 备份:定期备份
homework.db和uploads/目录。 - 扩展:高负载时,用PostgreSQL替换SQLite:修改
app.config['SQLALCHEMY_DATABASE_URI']为postgresql://user:pass@localhost/db。
常见部署问题
- 端口占用:检查
netstat -tuln | grep 5000,杀死进程kill <PID>。 - 权限错误:确保
uploads/目录可写:chmod -R 755 uploads/。 - 依赖缺失:运行
pip freeze > requirements.txt更新依赖。
部署完成后,系统即可使用。教师创建作业,学生登录提交。
学生如何解决作业提交失败与文件上传错误问题
学生在使用系统时,常遇到提交失败(如网络错误、认证失效)或文件上传错误(如格式不支持、大小超限)。以下是详细诊断和解决步骤,按问题类型分类。每个步骤包括原因分析、检查方法和完整示例。
1. 作业提交失败(一般提交错误)
原因:网络中断、会话过期、服务器错误或数据库问题。症状:点击提交后页面无响应或显示“提交失败”。
解决步骤:
检查网络连接:
- 确保Wi-Fi稳定。测试:在浏览器开发者工具(F12 > Network)查看请求是否发送。
- 示例:如果看到“Failed to load resource: net::ERR_CONNECTION_REFUSED”,说明服务器未运行。联系管理员重启服务器(
sudo systemctl restart gunicorn)。
验证登录状态:
- 会话可能过期(默认30分钟)。症状:重定向到登录页。
- 解决:重新登录。如果频繁发生,修改
app.py中login_manager的session_protection为strong。 - 代码修改示例(在
app.py添加):login_manager.session_protection = "strong" # 增强会话保护
检查表单数据:
- 确保作业ID正确。开发者工具中检查POST请求的payload(应包含
file和CSRF token)。 - 如果是空表单,刷新页面重填。
- 确保作业ID正确。开发者工具中检查POST请求的payload(应包含
查看浏览器控制台错误:
- F12 > Console:常见错误如“413 Payload Too Large”(文件太大)或“500 Internal Server Error”(服务器bug)。
- 示例:如果看到“TypeError: ‘NoneType’ object is not callable”,可能是数据库未初始化。运行
python app.py重新创建表。
联系教师/管理员:
- 如果以上无效,提供截图和时间戳。管理员可检查服务器日志:
tail -f /var/log/gunicorn/error.log(生产环境)。
- 如果以上无效,提供截图和时间戳。管理员可检查服务器日志:
预防:使用Chrome/Firefox,避免IE。提交前保存草稿(手动复制描述)。
2. 文件上传错误
原因:文件类型不支持、大小超限、浏览器兼容性或路径权限问题。症状:上传卡住、提示“上传失败”或文件未保存。
解决步骤:
检查文件大小和格式:
- 系统限制5MB,支持PDF/DOCX/TXT。症状:文件太大时,服务器返回“413 Request Entity Too Large”。
- 解决:
- 压缩文件:使用在线工具如Smallpdf。
- 转换格式:Word转PDF(文件 > 另存为 > PDF)。
- 示例:如果上传10MB视频失败,拆分成小文件或联系教师放宽限制(修改
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024)。
浏览器兼容性:
- 问题:旧浏览器不支持HTML5文件API。
- 解决:更新浏览器到最新版(Chrome 90+)。测试:在无痕模式下上传。
- 如果是移动端,切换到桌面浏览器。
检查文件名和特殊字符:
secure_filename会移除危险字符,但如果文件名含中文/特殊符号,可能失败。- 解决:重命名文件为英文(如
homework.pdf),再上传。 - 示例:原文件名“我的作业@2023.pdf” → 改为“my_homework_2023.pdf”。
权限和路径错误:
- 服务器端:检查
uploads/目录权限。症状:日志显示“Permission denied”。 - 解决:运行
chmod 777 uploads/(临时),或chown www-data:www-data uploads/(生产环境)。 - 验证:上传后,检查服务器文件:
ls uploads/应看到文件。
- 服务器端:检查
调试上传过程:
- 在浏览器Network标签查看POST请求:如果状态码200但文件未保存,可能是
app.py的file.save()路径错误。 - 临时调试:在
submit_homework路由添加打印:print(f"Saving to: {file_path}") # 检查控制台输出 - 如果是跨域问题(部署在不同域),添加CORS:
pip install flask-cors,在app.py添加:from flask_cors import CORS CORS(app)
- 在浏览器Network标签查看POST请求:如果状态码200但文件未保存,可能是
备用提交方式:
- 如果上传持续失败,教师可允许邮件提交作为临时方案。
- 预防:提交前用
ls -lh检查文件大小,确保<5MB。
学生提示:提交后立即刷新仪表盘,确认“我的提交”中出现记录。如果文件未显示,检查是否为浏览器缓存(Ctrl+F5刷新)。
教师如何解决作业提交失败与文件上传错误问题
教师角色主要是管理,但也会遇到学生反馈的提交问题,或自己创建作业时的错误。以下是针对教师的解决指南,聚焦管理和服务器端。
1. 学生提交失败的诊断
原因:学生端问题(如上所述),或教师端作业配置错误(如截止日期已过)。
解决步骤:
检查作业状态:
- 登录教师仪表盘,查看作业截止日期。如果已过,学生无法提交(需修改
submit_homework路由添加日期检查)。 - 代码示例(在
submit_homeworkPOST前添加):from datetime import datetime if datetime.now() > homework.deadline: flash('作业已截止') return redirect(url_for('dashboard'))
- 登录教师仪表盘,查看作业截止日期。如果已过,学生无法提交(需修改
查看服务器日志:
- 运行
tail -f app.log(需配置日志:在app.py添加import logging; logging.basicConfig(filename='app.log', level=logging.INFO))。 - 常见日志: “IntegrityError”(数据库约束失败,如重复提交)→ 清空数据库重试:
db.drop_all(); db.create_all()。
- 运行
模拟学生提交:
- 用测试账号登录,尝试提交。确认服务器响应。
- 如果是数据库锁定,重启Gunicorn:
pkill gunicorn; gunicorn -w 4 -b 0.0.0.0:5000 app:app。
批量处理:
- 如果多个学生失败,检查服务器负载:
top命令。如果CPU高,减少Gunicorn工作进程。
- 如果多个学生失败,检查服务器负载:
2. 文件上传错误(教师视角)
原因:服务器存储问题、批量上传失败或下载错误。
解决步骤:
检查上传目录:
- 确保
uploads/有足够空间:df -h。 - 如果文件损坏,验证
file.save()路径:打印file_path并手动检查文件完整性(file <path>命令)。
- 确保
处理批量下载/查看:
- 在
view_submissions.html添加批量下载链接:
在<a href="{{ url_for('download_all', homework_id=homework.id) }}">下载所有</a>app.py添加路由:
安装import zipfile @app.route('/download_all/<int:homework_id>') @login_required def download_all(homework_id): submissions = Submission.query.filter_by(homework_id=homework_id).all() zip_path = f"temp_{homework_id}.zip" with zipfile.ZipFile(zip_path, 'w') as zf: for sub in submissions: zf.write(sub.file_path, os.path.basename(sub.file_path)) return send_file(zip_path, as_attachment=True, download_name=f'homework_{homework_id}.zip')pip install flask(已包含)。
- 在
安全清理:
- 定期删除旧文件:添加cron任务
0 2 * * * find /path/to/uploads -mtime +30 -delete(每天凌晨2点删除30天前文件)。 - 如果上传失败日志显示“OSError: [Errno 28] No space left”,扩展磁盘或清理。
- 定期删除旧文件:添加cron任务
学生支持:
- 提供FAQ:如“上传失败?检查大小<5MB,格式PDF”。
- 如果学生报告特定错误,检查浏览器版本,建议升级。
教师提示:创建作业时,明确说明文件要求(如“仅PDF,<5MB”)。测试系统后,再开放给学生。
结论
这个Web作业提交系统源码简单高效,通过上述部署教程,您可以快速上线。学生和教师遇到提交或上传问题时,按步骤诊断,通常能快速解决。常见问题多源于网络、浏览器或配置,优化后系统稳定运行。如果需要更多功能(如邮件通知或移动端适配),可基于源码扩展。建议在GitHub上开源项目,便于社区贡献和更新。如果部署中遇到具体错误,欢迎提供日志细节进一步指导。
