引言

在现代教育环境中,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模型使用密码哈希确保安全。HomeworkSubmission关联用户和作业。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:环境准备

  1. 安装Python:确保服务器有Python 3.8+。

    sudo apt update
    sudo apt install python3 python3-pip python3-venv
    
  2. 创建虚拟环境(推荐,避免依赖冲突)。

    mkdir homework_system
    cd homework_system
    python3 -m venv venv
    source venv/bin/activate
    
  3. 上传源码:将上述文件复制到服务器(使用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服务器)。
    
    pip install gunicorn
    gunicorn -w 4 -b 0.0.0.0:5000 app:app
    
    这会在端口5000运行4个工作进程。

步骤5:配置Nginx和HTTPS(生产推荐)

  1. 安装Nginx

    sudo apt install nginx
    
  2. 配置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

  3. 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.dbuploads/目录。
  • 扩展:高负载时,用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. 作业提交失败(一般提交错误)

原因:网络中断、会话过期、服务器错误或数据库问题。症状:点击提交后页面无响应或显示“提交失败”。

解决步骤

  1. 检查网络连接

    • 确保Wi-Fi稳定。测试:在浏览器开发者工具(F12 > Network)查看请求是否发送。
    • 示例:如果看到“Failed to load resource: net::ERR_CONNECTION_REFUSED”,说明服务器未运行。联系管理员重启服务器(sudo systemctl restart gunicorn)。
  2. 验证登录状态

    • 会话可能过期(默认30分钟)。症状:重定向到登录页。
    • 解决:重新登录。如果频繁发生,修改app.pylogin_managersession_protectionstrong
    • 代码修改示例(在app.py添加):
      
      login_manager.session_protection = "strong"  # 增强会话保护
      
  3. 检查表单数据

    • 确保作业ID正确。开发者工具中检查POST请求的payload(应包含file和CSRF token)。
    • 如果是空表单,刷新页面重填。
  4. 查看浏览器控制台错误

    • F12 > Console:常见错误如“413 Payload Too Large”(文件太大)或“500 Internal Server Error”(服务器bug)。
    • 示例:如果看到“TypeError: ‘NoneType’ object is not callable”,可能是数据库未初始化。运行python app.py重新创建表。
  5. 联系教师/管理员

    • 如果以上无效,提供截图和时间戳。管理员可检查服务器日志:tail -f /var/log/gunicorn/error.log(生产环境)。

预防:使用Chrome/Firefox,避免IE。提交前保存草稿(手动复制描述)。

2. 文件上传错误

原因:文件类型不支持、大小超限、浏览器兼容性或路径权限问题。症状:上传卡住、提示“上传失败”或文件未保存。

解决步骤

  1. 检查文件大小和格式

    • 系统限制5MB,支持PDF/DOCX/TXT。症状:文件太大时,服务器返回“413 Request Entity Too Large”。
    • 解决:
      • 压缩文件:使用在线工具如Smallpdf。
      • 转换格式:Word转PDF(文件 > 另存为 > PDF)。
      • 示例:如果上传10MB视频失败,拆分成小文件或联系教师放宽限制(修改app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024)。
  2. 浏览器兼容性

    • 问题:旧浏览器不支持HTML5文件API。
    • 解决:更新浏览器到最新版(Chrome 90+)。测试:在无痕模式下上传。
    • 如果是移动端,切换到桌面浏览器。
  3. 检查文件名和特殊字符

    • secure_filename会移除危险字符,但如果文件名含中文/特殊符号,可能失败。
    • 解决:重命名文件为英文(如homework.pdf),再上传。
    • 示例:原文件名“我的作业@2023.pdf” → 改为“my_homework_2023.pdf”。
  4. 权限和路径错误

    • 服务器端:检查uploads/目录权限。症状:日志显示“Permission denied”。
    • 解决:运行chmod 777 uploads/(临时),或chown www-data:www-data uploads/(生产环境)。
    • 验证:上传后,检查服务器文件:ls uploads/ 应看到文件。
  5. 调试上传过程

    • 在浏览器Network标签查看POST请求:如果状态码200但文件未保存,可能是app.pyfile.save()路径错误。
    • 临时调试:在submit_homework路由添加打印:
      
      print(f"Saving to: {file_path}")  # 检查控制台输出
      
    • 如果是跨域问题(部署在不同域),添加CORS:pip install flask-cors,在app.py添加:
      
      from flask_cors import CORS
      CORS(app)
      
  6. 备用提交方式

    • 如果上传持续失败,教师可允许邮件提交作为临时方案。
    • 预防:提交前用ls -lh检查文件大小,确保<5MB。

学生提示:提交后立即刷新仪表盘,确认“我的提交”中出现记录。如果文件未显示,检查是否为浏览器缓存(Ctrl+F5刷新)。

教师如何解决作业提交失败与文件上传错误问题

教师角色主要是管理,但也会遇到学生反馈的提交问题,或自己创建作业时的错误。以下是针对教师的解决指南,聚焦管理和服务器端。

1. 学生提交失败的诊断

原因:学生端问题(如上所述),或教师端作业配置错误(如截止日期已过)。

解决步骤

  1. 检查作业状态

    • 登录教师仪表盘,查看作业截止日期。如果已过,学生无法提交(需修改submit_homework路由添加日期检查)。
    • 代码示例(在submit_homework POST前添加):
      
      from datetime import datetime
      if datetime.now() > homework.deadline:
       flash('作业已截止')
       return redirect(url_for('dashboard'))
      
  2. 查看服务器日志

    • 运行tail -f app.log(需配置日志:在app.py添加import logging; logging.basicConfig(filename='app.log', level=logging.INFO))。
    • 常见日志: “IntegrityError”(数据库约束失败,如重复提交)→ 清空数据库重试:db.drop_all(); db.create_all()
  3. 模拟学生提交

    • 用测试账号登录,尝试提交。确认服务器响应。
    • 如果是数据库锁定,重启Gunicorn:pkill gunicorn; gunicorn -w 4 -b 0.0.0.0:5000 app:app
  4. 批量处理

    • 如果多个学生失败,检查服务器负载:top命令。如果CPU高,减少Gunicorn工作进程。

2. 文件上传错误(教师视角)

原因:服务器存储问题、批量上传失败或下载错误。

解决步骤

  1. 检查上传目录

    • 确保uploads/有足够空间:df -h
    • 如果文件损坏,验证file.save()路径:打印file_path并手动检查文件完整性(file <path>命令)。
  2. 处理批量下载/查看

    • 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(已包含)。
  3. 安全清理

    • 定期删除旧文件:添加cron任务 0 2 * * * find /path/to/uploads -mtime +30 -delete(每天凌晨2点删除30天前文件)。
    • 如果上传失败日志显示“OSError: [Errno 28] No space left”,扩展磁盘或清理。
  4. 学生支持

    • 提供FAQ:如“上传失败?检查大小<5MB,格式PDF”。
    • 如果学生报告特定错误,检查浏览器版本,建议升级。

教师提示:创建作业时,明确说明文件要求(如“仅PDF,<5MB”)。测试系统后,再开放给学生。

结论

这个Web作业提交系统源码简单高效,通过上述部署教程,您可以快速上线。学生和教师遇到提交或上传问题时,按步骤诊断,通常能快速解决。常见问题多源于网络、浏览器或配置,优化后系统稳定运行。如果需要更多功能(如邮件通知或移动端适配),可基于源码扩展。建议在GitHub上开源项目,便于社区贡献和更新。如果部署中遇到具体错误,欢迎提供日志细节进一步指导。