☰
课程作业与实验管理系统:前后端分离课设源码全解析
2026/10/1 18:40:36 网站建设 项目流程

每学期帮学弟学妹看课程设计代码,我遇到最多的题目就是“课程作业和实验管理系统”。这题目听起来不复杂,可真正要实现作业发布、实验报告上传、教师批改、成绩统计这条完整链路时,很多人第一版交上去是跑不通的。这篇文章基于一套完整的“可白嫖源码”课设项目来做案例分析,把“课程作业和实验管理系统”从需求、数据库表、接口设计、前端页面到跑通步骤完整拆开讲,适合正在选课设题、想直接改造一套现成源码的读者,也适合想学一个完整前后端分离项目怎么落地的人。

先说一个基本判断:这类系统技术难度不算高,难的是把学生、教师、管理员三种角色的权限和状态流转想清楚。只要这一层想明白,代码写起来很快。下面我按自己的理解,从需求讲到数据库,再讲到核心接口实现和上线跑通的完整过程,尽量说人话,不绕弯子。

1. 课程作业与实验管理的真实痛点:先搞清系统为谁而做

1.1 传统管理方式的三宗罪

为什么每届学生都在做这个题目?因为现实中太多课程还在用“微信群+Excel”管理作业。我见过最离谱的命名是“作业最终版2(新建).docx”,老师的电脑桌面堆了几十个这样的文件,期末统计成绩时一个个对学号,命名的同学甚至用拼音简写,比对学生名单能花一下午。

实验课的问题更明显。实验报告要么打印纸质版,要么一个个邮件发过来,老师要下载、归类、重命名、评分,最后再把成绩敲进Excel。遇到截止日期时,学生不知道到底提交成功没有,老师也不知道谁没交。课程作业和实验管理系统本质上就是来解决这三个问题的:文件归档、流程透明、成绩自动汇总。

1.2 三方角色的诉求差异

这类系统的功能边界,完全取决于三种角色的痛点。

学生最关心的三件事:什么时候截止、交成功没有、老师给多少分。所以学生端必须有清晰的待办任务列表,提交后要能看历史提交记录,成绩出来要能第一时间看到。

教师的诉求是“少干活”:能一键发布作业、能批量查看提交、能在线上给分写评语、能自动统计提交率和平均分。这里最容易被忽略的是“待批改数量提醒”。教师登录后第一眼应该看到还有多少份作业没批,而不是自己记着截止日期再去后台翻。

管理员的需求相对简单:管理用户账号、维护课程与专业信息、查看整个系统的使用情况。很多课设把管理员做成最高权限角色,能看所有数据,这也是可以的,但权限一定要和教师、学生彻底分开,不能出现学生接口能查到所有用户的情况。

1.3 功能优先级怎么排

我见过很多学生把这个系统越做越大,加了一堆聊天、点赞、动态功能,最后作业提交的核心链路反而没做完。如果你做课设,优先级应该这样排:

  • 第一优先级:登录注册、作业/实验发布、文件提交、列表查看、教师批改打分
  • 第二优先级:选课关系统计、提交率/平均分报表、实验与作业分类管理
  • 第三优先级(有余力再做):公告通知、个人中心头像、导成绩Excel、图表可视化

把第一优先级做完,系统已经能用了。第二优先级是答辩时的加分点。第三优先级属于锦上添花,别让它拖垮主线。

2. 技术选型与源码结构:为什么推荐 Spring Boot + Vue 前后端分离

2.1 这套源码用到的技术栈

我整理过的课设源码里,用 Java 后端的占了很大比例,尤其是 Spring Boot 这套组合。以下是这套源码默认的技术栈:

层次选型说明
后端框架Spring Boot 2.7稳定、配置少、资料多
ORMMyBatis-Plus单表CRUD几乎不用写SQL
数据库MySQL 8.0utf8mb4,支持中文友好
认证方式JWT Token无状态登录,适合前后端分离
前端框架Vue 2 + Element UI组件成熟,课设资料最全
前端请求Axios + Vue Router统一拦截token和错误码
构建工具Maven / npm后端打包、前端启动

2.2 为什么不推荐传统 JSP、HTML 直接塞后端

很多学校的课程还在教 JSP,但你要是在课设里用 JSP 写页面,会发现自己调试起来非常痛苦:改一个按钮样式要重启服务,接口和数据层全耦合在一起,答辩时也说不清楚模块边界。

前后端分离最大的优势,是把“后端接口”和“前端页面”彻底分开。你可以先用 Postman 测接口,接口全通了再去写页面,最后联调时问题容易定位。而且以后想换皮肤、换前端框架,后端代码一句都不用动。对课程设计来说,接口文档还是答辩时可展示的加分内容,你在 PPT 里贴上 API 列表,比贴一堆页面截图更有说服力。

如果你的导师更偏向 Python,这套表结构和接口设计也可以很轻松换成 Flask 或 Django 重写,因为核心设计完全一样,差的无非是框架语法。这一点待会讲到数据库设计时你会更清楚。

2.3 源码目录结构速览

拿到源码后第一件事不是看业务代码,而是先看目录结构。这套源码大概长这样:

course-assignment-system/ ├── backend/ │ ├── pom.xml │ ├── src/main/java/com/example/course/ │ │ ├── controller/ # 接口层:路由入口 │ │ ├── service/ # 业务逻辑层:核心判断 │ │ ├── mapper/ # MyBatis-Plus 数据访问 │ │ ├── entity/ # 数据库实体 │ │ ├── common/ # 统一返回结果、异常处理 │ │ ├── config/ # 跨域配置、JWT拦截器 │ │ └── CourseApplication.java │ └── src/main/resources/ │ ├── application.yml # 数据库和文件上传配置 │ └── mapper/ # 复杂SQL的XML文件 └── frontend/ ├── package.json ├── vue.config.js # 开发环境代理配置 └── src/ ├── api/ # 所有请求方法统一封装 ├── router/ # 路由表,含角色判断 ├── store/ # 登录用户信息 ├── views/ # 学生端、教师端、管理端页面 └── permission.js # 全局路由守卫

为什么要保持这种分层?因为你在答辩时可以说“这是我按三层架构设计的”,而不是“代码都在一个文件里”。三层架构的好处是:controller 只管接收参数和返回结果,service 负责业务规则,mapper 只做数据库操作。后面要改任何逻辑,都知道去哪改。

3. 数据库设计拆解:六张核心表撑起整套系统

3.1 用户与课程基础表

数据库设计决定了这个系统最多能做什么。这套源码里最核心的是用户表、课程表、选课关系表。

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_no VARCHAR(32) NOT NULL COMMENT '学号或工号', real_name VARCHAR(32) NOT NULL COMMENT '姓名', password VARCHAR(100) NOT NULL COMMENT '加密存储', role VARCHAR(16) NOT NULL COMMENT 'admin/teacher/student', department_name VARCHAR(64) COMMENT '院系或专业', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

用户表的关键在于 role 字段,而不是单独建一张角色表。很多课设为了炫技,搞了五张表的 RBAC 权限模型,最后自己都理不清。对于课程作业和实验管理系统来说,角色就三种,直接在用户表里加一个 role 字段最简单清晰,查询也不需要 JOIN。

课程表存课程基本信息,包括课程名称、课程编号、授课教师ID、学期、学分。选课关系表存课程和学生之间的关系,也就是谁选了哪门课。

CREATE TABLE course_student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, course_id BIGINT NOT NULL, student_id BIGINT NOT NULL, status TINYINT DEFAULT 1 COMMENT '1在修 0退课' );

这张表特别重要,因为后面算提交率时,分母不是系统里注册了多少学生,而是“选修这门课的学生数”。很多人算提交率直接用提交总数除以所有学生总数,明显是错的。

3.2 作业与实验两种业务分开建模

作业和实验看着相似,但实际操作不同:作业通常是一次性任务,可能有截止时间;实验一般会安排多个实验项目,可能要求提交报告和代码文件。所以这套源码把它们设计成两张表,分别叫 assignment 和 experiment。

CREATE TABLE assignment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, course_id BIGINT NOT NULL, title VARCHAR(128) NOT NULL, content TEXT COMMENT '作业要求', deadline DATETIME NOT NULL COMMENT '截止时间', status TINYINT DEFAULT 1 COMMENT '1已发布 0已关闭' ); CREATE TABLE experiment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, course_id BIGINT NOT NULL, exp_name VARCHAR(128) NOT NULL, content TEXT COMMENT '实验指导书内容', start_time DATETIME, end_time DATETIME, status TINYINT DEFAULT 1 );

有人会问:“作业和实验结构差不多,为什么不合并成一张 task 表,加个 type 字段?”这是可以的,但分开建表在实际开发中扩展性更好。比如实验需要填“实验地点”,作业不需要;作业可以设置“是否允许补交”,实验可能不需要单独设置。两种表分开,后面加字段互不影响,查询时语义也更清楚。

3.3 提交记录表:状态字段设计的重点

整套系统里最关键的就是提交记录表。无论是作业提交还是实验报告提交,都往这张表里插数据。

CREATE TABLE submission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_type VARCHAR(16) NOT NULL COMMENT 'assignment/experiment', biz_id BIGINT NOT NULL COMMENT '对应作业或实验ID', student_id BIGINT NOT NULL, content TEXT COMMENT '文字说明', file_url VARCHAR(255) COMMENT '附件路径', submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 1 COMMENT '1已提交 2已批改', score DECIMAL(5,1), comment_text VARCHAR(255) COMMENT '教师评语', review_time DATETIME COMMENT '批改时间' );

我之所以说这张表容易出事,是因为很多人会忽略一个核心问题:允许学生重复提交吗?如果允许,那同一学生同一作业就可能有两条记录,查询时要按 submit_time 倒序取最新;如果不允许,就需要加唯一约束,第二次提交要么被拒绝,要么走“先删旧再插新”。

这套源码采用的做法是:允许重复提交,新提交记录会生成新的记录,查询列表时按“学生+任务ID”分组取最新一条。这样历史提交记录都保留在库里,教师能看到学生交了几次,答辩时被问到“重复提交怎么处理”也有话说。注意,这里的 status 字段不是用来区分“几次提交”的,而是用来标识教师是否批改过。批改前后端展示逻辑完全不同。

3.4 没有外键的设计是否合理

这套源码里所有表都没有物理外键,只保留了逻辑关联,比如 submission 表里的 student_id 逻辑上对应 sys_user 表。这是我建议课设采用的做法:物理外键在插入、删除时会被数据库约束拦住,报错信息不直观,而且删除一个老师时如果课程表还有数据,整个删除直接失败。用逻辑外键,业务层自己控制关联关系,改起来灵活,查询性能也更好。

4. 核心业务逻辑实现:从提交作业到统计成绩一条线讲清

4.1 登录鉴权与三层权限控制

权限控制是一道送分题,也是最容易被问出漏洞的地方。这套源码的权限分了三层:

第一层是前端路由守卫。登录时后端返回的角色字段被存在 Vuex 里,路由表配置了 meta.roles,守卫里判断当前用户角色是否在允许列表里,不在就跳转回首页。这层的作用是让普通学生看不到管理端入口,提升体验。

第二层是后端 JWT 拦截器。用户登录成功后,后端生成一个包含用户ID和角色的 token,返回给前端。前端每次请求都把 token 放在请求头的 Authorization 字段里。后端用一个拦截器解析 token,解析失败直接返回 401,请求根本进不到 controller。这层保证了接口不是“前端隐藏了就安全”,哪怕有人绕过前端直接调用接口,没有 token 也一样拿不到数据。

第三层是接口内部的用户归属判断。比如教师查询“我的课程”时,不是传课程ID从表里查,而是从 token 里取出 teacher_id,再按这个ID查询课程。学生提交作业时,后端从 token 里取 student_id,不信任前端传来的任何“当前用户ID”。这是很多人会踩的坑:前端传什么就信什么,结果换个人提交就能写进别人的记录,答辩时被老师一问直接懵。

4.2 作业提交的后端双重校验

前端页面上,作业过了截止日期,提交按钮会灰掉,但这只是“用户体验”。真正的校验必须放在后端,因为任何人都可以绕过前端直接调用接口,如果后端不校验截止时间,凌晨三点照样能提交,系统就失去了意义。

后端处理提交的核心逻辑大概是这样的:

public Result submit(SubmitRequest req) { // 从token获取当前学生ID Long studentId = JwtUtil.getUserId(); // 校验任务是否存在 Assignment assignment = assignmentMapper.selectById(req.getAssignmentId()); if (assignment == null) { return Result.error("作业不存在"); } // 校验时间,只能用后端时间为准 if (LocalDateTime.now().isAfter(assignment.getDeadline())) { return Result.error("已过截止时间,无法提交"); } // 校验学生是否选了这门课 CourseStudent cs = courseStudentMapper.selectOne( new QueryWrapper<CourseStudent>() .eq("course_id", assignment.getCourseId()) .eq("student_id", studentId)); if (cs == null) { return Result.error("未选修该课程"); } // 插入提交记录 submissionMapper.insert(buildEntity(studentId, req)); return Result.success(); }

这一段逻辑里有三层校验:任务存在性、时间合法性、选课关系。很多课设源码只做第一层,后面两层全没有。但你想,一个没选课的学生也能交作业,那成绩表不就乱套了吗?这一块是答辩时最值得讲的点。

关于文件上传,源码用的是本地磁盘存储方案,在 application.yml 里配一个 upload.path 目录,上传的文件重命名为 UUID 加后缀,存储到磁盘,数据库只存相对路径。这样设计是为了避免中文文件名乱码和同名覆盖。千万别用原文件名直接存,两个学生都交 main.py 会互相覆盖,血泪教训。

我建议你在文件上传接口里再做一个类型白名单校验,只允许 doc、docx、pdf、zip、jpg、png 这些常见格式。不是限制用户,而是防止有些人传个 html 文件存进服务器,留下隐患。扩展名要从文件原始名称里取,不能信任前端传的 content-type,因为那个可以随便伪造。

4.3 教师批改与成绩统计

教师端批改列表的查询很典型:首先从 token 拿 teacher_id,再查出这个教师的所有课程 ID 集合,然后用课程 ID 集合去查对应的作业集合,最后查这些作业下的所有提交记录。这个链路涉及多次查询,如果只在 mapper 里写一个超级复杂的联表 SQL,反而容易出错。

我的建议是拆开查,查询次数多一些没关系,课程设计的数据量根本不用担心性能。代码可读性比微秒级的查询更快更重要。

统计逻辑上,最容易被追问的是提交率和平均分怎么算。答案要记牢:

  • 提交率 = 这门课程下“某作业/实验的已提交人数” ÷ “选课学生总数”
  • 平均分 = 所有已批改提交记录的成绩平均值,未批改的不参与计算
-- 某作业的提交人数 SELECT COUNT(DISTINCT student_id) FROM submission WHERE biz_type='assignment' AND biz_id = #{assignmentId}; -- 某课程的选课人数 SELECT COUNT(*) FROM course_student WHERE course_id = #{courseId} AND status = 1; -- 某作业的平均分 SELECT AVG(score) FROM submission WHERE biz_type='assignment' AND biz_id = #{assignmentId} AND status = 2 AND score IS NOT NULL;

这三条 SQL 写对,统计功能就稳了。注意最后一条 SQL 加了 score IS NOT NULL,而不是只靠 status=2,因为有可能出现教师把状态改成已批改但没填分数的情况,默认分数为空,AVG 会忽略空值,但保险起见最好过滤。

4.4 三个角色的页面组织方式

前端页面按角色分目录组织,这个方式建议沿用:

views/ ├── student/ │ ├── AssignmentList.vue # 待交作业列表 │ ├── ExperimentList.vue # 实验列表 │ └── MyScores.vue # 我的成绩 ├── teacher/ │ ├── CourseMine.vue # 我的课程 │ ├── AssignmentPublish.vue # 发布作业 │ ├── SubmissionReview.vue # 批改提交 │ └── StatisticsDashboard.vue # 课程统计 └── admin/ ├── UserManage.vue # 用户管理 ├── CourseManage.vue # 课程管理 └── GlobalStats.vue # 全局数据面板

学生端和教师端最核心的交互就是“待办列表”。学生登录后看到的待办列表,本质上是查自己选修的所有课程 → 再查这些课程下所有已发布的作业/实验 → 再过滤掉自己已经提交过的。这三步是嵌套查询,初学者容易在联表时搞混,前端直接调一个专门的接口比较省事,比如/api/student/todo-list,后端一次性封装好返回结构。

5. 快速跑通这套源码:环境配置、启动与高频报错处理

5.1 本地环境准备

要把这套源码在本地跑起来,你需要准备这些环境:

组件版本建议说明
JDK8 或 11Spring Boot 2.7 都支持
Maven3.6+后端依赖管理
Node.js14 或 16Vue 2 项目不要用 Node 20 以上,容易编译报错
MySQL5.7 或 8.08.0 需要注意连接驱动
IDEIDEA / VSCode后端用 IDEA 更顺手

数据库先建好,注意字符集:

CREATE DATABASE course_assignment DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

然后把源码里的 SQL 文件导入,或者直接用 MyBatis-Plus 的自动建表功能,看 README 怎么写。如果源码里有 init.sql,优先用 init.sql,因为除了表结构,它还包含初始化数据和默认账号。

5.2 后端前端分别启动

后端启动比较简单。用 IDEA 打开 backend 目录,等 Maven 依赖下载完,修改 application.yml 里的数据库账号密码和上传目录:

spring: datasource: url: jdbc:mysql://localhost:3306/course_assignment?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8 username: root password: 你自己的密码 upload: path: D:/course-upload

直接运行 CourseApplication 类。看到 Tomcat started on port(s): 8080 就是成功。

前端启动要留意 npm 源的问题,国内网络环境用官方源下载依赖容易卡住,建议提前换镜像源:

npm config set registry https://registry.npmmirror.com npm install npm run serve

启动成功后访问 http://localhost:8081,如果 vue.config.js 里配了代理,前端请求会转发到 8080 端口。跨域问题就直接绕过了,不用再单独配。

5.3 高频报错与解决办法

我整理了跑这套源码时最容易遇到的一批报错,都是课设群里高频出现的:

报错现象原因对应方案
Access denied for user数据库账号密码错误检查 application.yml,别用 Navicat 里能连就当配置对
Server returns invalid timezoneMySQL 8.0 时区问题URL 上加 serverTimezone=Asia/Shanghai
Port 8080 was already in use端口被占用改 application.yml 的 server.port
Cannot find module 'node-sass'Node 版本和依赖版本不匹配Node 换回 14,或删除 node_modules 重新 npm install
前端请求全部 404接口路径错误或代理没生效检查 vue.config.js 的 proxy 和 axios baseURL
文件上传报 500 或失败上传目录不存在或没有写权限手动创建上传目录,Windows 别用 C 盘根目录
JWT token 过期本地调试时间过长重新登录,或调大 token 过期时间配置

5.4 三分钟验证系统闭环

跑起来之后,别急着看页面好不好看,先验证核心链路通不通。用学生账号登录 → 找一门课 → 上传一份测试文件 → 用教师账号登录 → 找到对应提交 → 打分写评语 → 再用学生账号刷新 → 看到成绩和评语。如果这条链路没问题,系统的主干功能已经可用了。

这条验证路径的价值在于,它走遍了用户、课程、选课、作业、提交、批改六张表。答辩时老师问“你怎么测的”,你就可以说:我用了一个真实的业务闭环验证,而不是只看页面能不能打开。这个回答比“我打开看没问题”高级得多。

6. 拿到源码后怎么改成自己的课设:改造点与答辩思路

6.1 先把“别人的痕迹”清干净

白嫖的源码一定要先改表面信息,否则一开 PPT 就露馅。重点改这几处:前端登录页的标题、导航栏的 logo、浏览器标签页的 favicon、页脚版权信息、后端接口文档里的作者注释。

前端登录页标题一般在登录组件里写死,比如 “课程作业管理系统”,改成自己学校名称和自己的项目名字。favicon 很多人会忽略,浏览器标签页上还挂着别人系统的图标,答辩评委眼皮底下很尴尬。

数据库里的初始管理员账号、联系我们电话这些也别漏了。清理这一步花不了十分钟,但能让你后面说“这是我自己完善过的项目”时底气更足。

6.2 低成本加需求,优先加这三个方向

想从“抄代码”变成“做了项目”,关键在于加一个能说清楚“我自己写”的功能。我建议优先加以下三类之一:

第一,导出成绩 Excel。用 EasyExcel 或 POI 写一个导出接口,教师点击按钮就能把当前课程所有学生的成绩导出成 Excel。这个功能实用、工作量适中、答辩时可以直接现场演示,导出文件的表头、格式都是你控制,讲起来有内容。

第二,用 ECharts 做成绩分布图。前端引入 ECharts,统计某个作业各分数段人数,画一个饼图或柱状图。这能体现你对前端组件的掌握,而且视觉效果好,放在答辩 PPT 里很占优势。

第三,公告通知模块。管理员或教师发布公告,学生登录后首页展示。实现只需要一张 notice 表加两个接口,但系统完整度会明显提升。

我自己更推荐导出 Excel,因为教师用它最多,演示效果直观,而且后端代码量不大,非常适合作为“我的核心贡献”来讲。

6.3 答辩时老师最爱问的三个问题和应对思路

根据往年经验,答辩老师围着这类系统最常问这三个问题,回答思路提前准备好:

第一个,“权限是怎么控制的?”不要说“我在页面里判断角色”。标准回答是:前端用路由守卫控制页面可见性,后端用 JWT 拦截器统一校验,接口内部再按 token 里的用户身份做数据归属过滤,三层配合。这样答既体现了分层思想,又证明你不是只在写页面。

第二个,“学生重复提交怎么处理?”如果源码允许重复提交,答:系统允许学生在截止前重复提交,并且每次提交都会生成新记录,历史记录保留,教师可查看提交次数。这就把一个设计决策讲成了一个特点。

第三个,“统计提交率和平均分的逻辑是什么?”答:提交率是以选课名单为分母,已提交人数为分子;平均分只统计已批改的提交记录。一句话就能讲清楚,但前提是你真把 SQL 写对了。

结尾分享一个我自己的体会:整理这套源码时最大的收获,不是某段代码写得多巧妙,而是发现了这类系统的通用骨架——“发布 → 提交 → 批改 → 统计”这条链路,换成考勤系统、问卷系统、报修系统,思路都是一模一样的。白嫖的源码一定要动手改,哪怕只是加一个导出 Excel 的小功能,答辩时被问到“你做了什么”时,至少能说出一件自己亲手写出来的东西。最后别忘了把数据库密码、上传路径、前端标题这些细节处理干净,祝课设顺利。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询