
简介一套基于Java的在线作业提交与批改系统源码内含完整数据库脚本面向Java Web学习者、高校师生及毕业设计者用于快速搭建可运行的作业管理平台。系统覆盖用户权限管理、作业发布、在线提交、自动批改与手动批改、成绩统计等核心环节数据库脚本包含用户表、作业表、提交表、评分表等基础结构适合作为课程设计或毕业设计的参考原型。技术实现上涉及Spring MVC、MyBatis/JPA持久化、Shiro/Spring Security权限控制、文件上传组件、富文本编辑器并提供文本相似度与语法检查等自动批改思路同时给出Redis缓存、Docker部署等优化方向有助于理解Java Web全流程开发。资源包以Java源文件、配置文件和SQL脚本为主压缩后约17.42MB便于导入IDE直接运行或二次改造。已有547人学习下载体现了其在教学实训与项目练习中的实用价值能够帮助开发者快速理解权限管理与文件上传的落地实现。1. 这套 Java 作业提交批改系统源码先想清楚拿它来填什么坑拿到这套 java 作业提交批改系统源码时我先翻的不是代码是数据库脚本。对这种带在线提交、批改、成绩统计的教学系统来说真正的复杂度不在文件上传而在三件事师生的权限怎么隔离、作业提交后的状态怎么流转待批改、已批改、需重交、成绩怎么回写。这套资源把这三件事串成了完整闭环——教师发布作业、学生提交文件、系统做相似度比对、教师打分写评语、按作业统计平均分和最高最低分。如果你正在做 Java 课程设计、想拿一套现成源码当骨架改造成毕设或者要给单位内部搭一个作业收集工具这套含数据库脚本的源码能省掉从零建表的大量时间尤其适合拿它来理解 Java Web 里权限、上传、批改、统计这一整条业务线是怎么落地的。2. 从目录到首次启动三条依赖链决定你能不能跑起来这类老 Java 项目最气人的地方是代码本身没问题环境配不对就是跑不起来。我习惯把这种问题叫环境玄学其实拆开看就是三条链环境链JDK、Tomcat、MySQL 的版本搭配、配置链连接串、路径、编码、数据链SQL 脚本能不能干净建库。这套源码拿到手先别急着改代码按下面三步走每一步都验证完再进下一步。2.1 拿到源码先看三个位置web.xml、lib 和 SQL 脚本很多课程设计类源码包没有 README技术栈全靠猜。不用猜看三个地方就够web.xml 决定入口框架lib 目录或 pom.xml 决定持久层和依赖版本sql 脚本决定数据库长什么样。常见目录结构大致如下job-submit-system/ ├── sql/ │ ├── init.sql # 建表 初始化数据 │ └── data.sql # 单独的数据脚本 ├── src/main/ │ ├── java/... # controller / service / dao / model │ ├── resources/ │ │ ├── jdbc.properties # 数据库连接配置 │ │ ├── mybatis/ # mapper XMLSQL 写在这里 │ │ └── spring/ # Spring / Spring MVC 配置 │ └── webapp/ │ ├── WEB-INF/web.xml # 项目入口第一优先级 │ └── static/ # 前端页面、CSS、JS └── pom.xml 或 build.xml第一个看 web.xml 里的 filter 和 servlet-mapping能立刻判断是 Spring MVC 还是 Struts 的老项目第二个看依赖里有没有 mybatis、mysql-connector-java判断持久层用的什么第三个看 sql 脚本开头是 CREATE DATABASE 还是直接 CREATE TABLE前者说明脚本会帮你建库后者要求你手动建。这一步做完你对这个项目是什么年代的东西心里就有数了后面配环境不会瞎试。2.2 环境匹配JDK、Tomcat、MySQL 谁配谁这类源码最常见的搭配是 JDK 1.8 Tomcat 8.5 MySQL 5.7这不是随便选的。很多老项目编译级别定在 1.8用 JDK 11 甚至 17 打开会报一堆 javax 包找不到的错误Tomcat 10 把 javax.servlet 迁到了 jakarta.servlet老代码直接跑不起来MySQL 8 的驱动类名和连接参数也和 5.7 不一样。所以环境不是越新越好而是越匹配越好。组件推荐版本说明JDK1.8老项目编译级别通常为 1.8新 JDK 会有 API 移除问题Tomcat8.5 / 9.0避免 Tomcat 10 的 javax 到 jakarta 迁移问题MySQL5.7 或 8.0用 8.0 的话驱动类要换连接串要加时区参数Maven3.6 左右对应 JDK 1.8太新的 Maven 也可能有兼容问题有个细节容易被忽略MySQL 8.0 出来后很多旧源码里的 driver 还是com.mysql.jdbc.Driver这个类在 8.0 里已经标记废弃但还能用真正会报错的是连接串少了serverTimezone参数。如果你是非要用 MySQL 8.0 不可记住两点驱动的类名换成com.mysql.cj.jdbc.DriverURL 末尾加?serverTimezoneAsia/Shanghai这两处是最常翻车的地方。2.3 部署三步走和一个验证清单第一步建库。用命令行或 IDE 执行 SQL 脚本执行前先确认脚本里有没有 CREATE DATABASE。用 IDEA 打开脚本文件时注意连接配置建议直接用命令行导入避免编码问题。常见做法是mysql -uroot -p sql/init.sql执行完查一下表是否齐全。这里有个提醒如果脚本里有 DROP TABLE说明设计者考虑到了重复执行如果只有 CREATE TABLE重复执行会报表已存在需要手动清库再跑。第二步改连接配置打开 jdbc.properties把账号密码改成你自己的jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/job_system?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456driver 类和 URL 里的编码参数就是前面说的坑useUnicodetruecharacterEncodingutf8这两项能不能对上直接决定后面页面上出现的是中文还是问号。第三步部署到 Tomcat把项目打成 war 包放到 webapps 下启动并看日志。cp job-submit.war /opt/tomcat/webapps/ /opt/tomcat/bin/startup.sh tail -f /opt/tomcat/logs/catalina.out启动成功的标志不是 Tomcat 起来了而是访问http://localhost:8080/项目名/能出登录页。我一般用这个清单验证部署有没有真的通管理员能登录、能创建一门课程作业、学生账号能上传文件、教师能打分、成绩页能出统计。这五个动作全部走一遍才算这套源码在你机器上真正落地了缺任何一个都说明还有隐藏问题。3. 数据库脚本是全套系统的地基五张表和一个成绩统计 SQL这套资源里最值得先看的就是数据库脚本。作业提交批改系统的数据流其实很直白教师建作业学生交作业教师批作业系统算成绩。落到表上就是用户、作业、提交、评分这四类数据再加一个记录用户角色的关联关系。把这五张表的结构吃透后面所有代码都看得懂。3.1 核心表结构用户、作业、提交、评分怎么关联常见的设计是五张表用户表直接带角色字段或者拆一张角色表做关联。先从简单的说起表名职责关键字段t_user用户管理员/教师/学生user_id, username, password, real_name, rolet_assignment作业assignment_id, teacher_id, title, content, deadline, total_scoret_submit作业提交记录submit_id, assignment_id, student_id, file_path, submit_timet_score评分记录score_id, submit_id, grader_id, score, comment, grading_timet_role可选角色表role_id, role_code, role_namerole字段是这套系统的权限开关常见的取值是 admin、teacher、student 三段。教师发布作业时teacher_id指向t_user.user_id学生提交时student_id指向学生评分的grader_id指向批改人。作业表和提交表是一对多提交表和评分表是一对一这也是为什么很多系统允许学生重复提交时设计上要做覆盖旧记录而不是新增一条——否则成绩统计的时候一个学生会出现两条记录。有一个关键约束容易被忽略同一个学生对同一份作业只能有一条有效提交。常见做法是在t_submit上加UNIQUE(assignment_id, student_id)约束或者在业务层做查重。如果你拿到手的脚本里没有这个约束建议自己加上否则后期成绩统计一定会乱。3.2 初始化数据里藏的角色账号数据库脚本里一般会带初始化数据管理员、测试教师、测试学生的账号都写在这里。拿到脚本后先查一下用户表把默认密码找出来不然登录页都进不去。SELECT user_id, username, role FROM t_user;看到的结果一般是三行左右的测试账号密码可能是明文也可能是 MD5 加密后的值。如果是加密值别慌去代码里找 MD5 工具类或者直接在 SQL 里把密码重置成已知值。这里有个习惯值得养成拿到任何带数据库脚本的 Java 源码第一件事就是把初始化账号和角色查出来写进自己的笔记里。后续验证部署、测试功能、演示项目都要用这批账号丢了它们你连门都进不去。我还遇到过一种情况脚本里只有建表语句没有初始化数据user 表是空的。这种就得手动插一条管理员账号不然系统起跑后没人能登录。常见做法是INSERT INTO t_user (username, password, real_name, role) VALUES (admin, MD5(123456), 系统管理员, admin);如果你用的 MySQL 版本不支持 MD5 函数也可以在代码里找到注册接口先注册一个再手动改 role 为 admin。总之先把入口打通后面什么都好说。3.3 成绩报表的三个常用 SQL成绩管理模块在代码层面其实不复杂核心是 SQL 的聚合查询。正文里说到的平均分、最高分、最低分也就是AVG、MAX、MIN三个聚合函数在 GROUP BY 上的应用。按作业统计的报表 SQL通常长这样SELECT a.assignment_id, a.title, COUNT(s.submit_id) AS submit_cnt, ROUND(AVG(sc.score), 1) AS avg_score, MAX(sc.score) AS max_score, MIN(sc.score) AS min_score FROM t_assignment a LEFT JOIN t_submit s ON a.assignment_id s.assignment_id LEFT JOIN t_score sc ON s.submit_id sc.submit_id GROUP BY a.assignment_id, a.title ORDER BY a.publish_time DESC;这里的关键是 LEFT JOIN不是 INNER JOIN。用 LEFT JOIN 才能把没人提交的作业也显示出来COUNT(s.submit_id)对没有提交的作业会返回 0而AVG(sc.score)会返回 NULL。如果你想看哪些学生没交作业思路反过来SELECT u.username, a.title FROM t_user u JOIN t_assignment a LEFT JOIN t_submit s ON s.student_id u.user_id AND s.assignment_id a.assignment_id WHERE u.role student AND s.submit_id IS NULL;这个 SQL 有个容易翻车的地方LEFT JOIN 的关联条件里同时带上了student_id和assignment_id两个维度如果只关联student_id会出现学生和所有作业交叉匹配查出来的未提交全是错的。这种多表关联查询在成绩管理模块里到处都是你改别人的源码时JOIN 条件一定逐行看清楚再动。4. 作业发布到批改的一条完整链路上传、相似度比对与成绩回写这个系统的业务主线就是三件事学生把文件传上来系统自动查重或教师手动看最后把分数写回数据库。很多刚接触的人以为难点在文件上传实际上上传只是入口真正的逻辑在后面——提交记录怎么防重、批改状态怎么流转、分数和状态怎么保证一致。这一章按链路顺序拆开讲。4.1 文件上传白名单校验和独立存储目录作业提交模块的核心接口是文件上传。常见的实现是基于 Spring MVC 的 MultipartFile配合校验逻辑。核心代码如下PostMapping(/submit) public String submit(RequestParam(file) MultipartFile file, RequestParam(assignmentId) Integer assignmentId, HttpSession session) { // 1. 限制文件大小超了直接拒绝别等写盘后才报错 if (file.getSize() 10 * 1024 * 1024) { throw new BizException(作业文件不能超过 10MB); } // 2. 扩展名白名单只允许常见文档和压缩包格式 String ext StringUtils.getFilenameExtension(file.getOriginalFilename()); if (!Arrays.asList(zip, rar, pdf, doc, docx, java).contains(ext)) { throw new BizException(不支持的文件类型); } // 3. 保存到独立目录路径按作业ID隔离文件名带userId防止互相覆盖 String dir /data/job_files/ assignmentId; Files.createDirectories(Paths.get(dir)); String saveName session.getAttribute(userId) _ System.currentTimeMillis() . ext; file.transferTo(new File(dir, saveName)); // 4. 写提交记录覆盖该学生此前的旧提交 return submitService.saveOrReplace(assignmentId, userId, saveName); }这段代码里有三个设计点值得照抄。一是扩展名用白名单而不是黑名单黑名单永远有漏网之鱼白名单把允许的类型写死最安全。二是存储目录用assignmentId隔离文件名带userId和时间戳这样同一份作业下不同学生的文件不会互相覆盖也不会因为重名把上一次作业卷掉。三是路径用绝对路径别用相对路径Tomcat 部署目录一清理相对路径下的文件就全没了。4.2 自动批改Jaccard 相似度怎么落地自动批改这块正文里提到的 Jaccard 相似度算法是文本比对场景最常用的方案。它的原理简单说来就是两个集合的交集大小除以并集大小值越接近 1 说明越相似。对于文本作业可以把句子拆成词集合再算相似度。核心实现如下public double jaccardSimilarity(String a, String b) { SetString setA splitWords(a); SetString setB splitWords(b); // 取并集 SetString union new HashSet(setA); union.addAll(setB); if (union.isEmpty()) { return 0.0d; // 两份空文本直接判 0 } // 取交集 SetString inter new HashSet(setA); inter.retainAll(setB); return (double) inter.size() / union.size(); }阈值怎么定是个经验问题。我一般会设两个档相似度超过 0.6 标黄超过 0.8 标红标红的自动进入教师复核列表。需要明确的是Jaccard 只能查重不能判断对错——两份作业互相抄了 90%或者一份作业抄了教材原文 90%算出来分数都高。所以自动批改的结果只能作为参考最终评分一定要落到手动批改。另外这种基于词集合的算法对语序不敏感A 影响 B和B 影响 A算出来相似度极高这也是它的天然局限别指望它替代人工。4.3 手动批改与成绩回写一个事务里完成两件事教师批改时前端通常是一个富文本编辑器加一个分数输入框后端要做的是在一个事务里同时完成评分插入和提交状态更新。这里的核心问题是分数写了但作业状态还停在待批改学生那边看不到成绩这是最典型的翻车现场。正确做法如下Transactional public void grade(Integer submitId, Integer score, String comment, Integer graderId) { // 1. 插入评分记录 Score scoreRecord new Score(submitId, score, comment, graderId); scoreDao.insert(scoreRecord); // 2. 同步更新提交表状态两条写在同一个事务里 submitDao.updateStatus(submitId, graded); // 3. 更新后触发后续逻辑通知学生、刷新成绩报表 notifyStudent(submitId); }Transactional是关键它保证评分插入和状态更新要么都成功要么都回滚。如果你在改源码时发现这两个操作不在同一个事务里就会出现分数已经入库但列表还显示待批改的中间状态。还有一个常见问题成绩报表里学生反复看到旧分数原因通常是评分表没有按student_id assignment_id的唯一约束或者更新时直接 INSERT 而不是 UPDATE。手动批改模块的正确逻辑是一个学生的一份作业只有一条评分记录重复批改是 UPDATE不是 INSERT。5. 运行与部署的常见问题五个坑和处理顺序老 Java Web 项目跑不起来绝大多数不是代码问题是环境配置问题。这里挑五个最常见的坑按照从外到内的排查顺序写先看部署层再看数据层最后看代码层。每一条都是实际踩过的现象、原因、解决一次给全。5.1 坑一Tomcat 启动成功但首页 404现象catalina.out 没有报错http://localhost:8080/项目名/访问直接 404改过端口也没用。原因有两个方向war 包实际没解压成功或者 application context 路径不对。Tomcat 解压失败时不会主动报错只在访问时给 404特别容易误判成代码问题。解决先去 webapps 下确认有没有解压出来的目录没有就手动删掉 war 包再放一次有目录但访问还是 404确认 URL 里的路径和目录名完全一致包括大小写。还有一个隐蔽原因Tomcat 的 work 目录缓存了旧的编译结果清掉work/Catalina再重启。5.2 坑二数据库连接报 Communications link failure现象启动项目时报错提示跟 MySQL 的连接建立失败有时还带Cannot create PoolableConnectionFactory。原因多数不是 MySQL 没启动而是驱动版本和连接参数不匹配。MySQL 5.7 时代用com.mysql.jdbc.Driver加短 URL 就能连换到 MySQL 8.0 后URL 里少了serverTimezone参数会直接建连失败。解决换驱动类com.mysql.cj.jdbc.DriverURL 改成jdbc:mysql://localhost:3306/job_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai。如果你不确定 MySQL 版本先在命令行里执行SELECT VERSION();确认后再改配置。5.3 坑三页面中文全是问号或乱码现象从数据库读出数据正常但页面显示中文变成??或者录入中文后存进数据库变乱码。原因通常有三层数据库连接串没指定编码、Tomcat 的 URI 编码不是 UTF-8、页面请求没走编码过滤器。逐层解决连接串里确认有characterEncodingutf8Tomcat 的 server.xml 里给 Connector 加URIEncodingUTF-8项目里如果有 CharacterEncodingFilter确认 URL 模式是/*而不是/。这三层缺一不可只改其中一处很可能从乱码变成局部乱码更头疼。5.4 坑四文件上传成功后一重启就找不到文件现象学生上传作业时提示成功文件确实存在但服务一重启文件在磁盘上就找不到或者所有人都能访问到别人的作业文件。原因上传路径写的是相对路径比如upload/这个目录在 Tomcat 解压后的临时目录里重启即清空另一种情况是保存路径没按作业和用户隔离所有文件混在一个目录里。解决把存储路径改成独立于项目的绝对路径比如/data/job_files/并按作业ID/用户ID/的层级组织文件。再配合下载时的权限校验避免学生遍历 URL 直接下载别人的作业。5.5 坑五高版本 JDK 打开项目一堆编译报错现象用 JDK 17 打开源码一堆javax包找不到、sun.misc相关报错或者编译级别不识别。原因老项目按 JDK 1.8 写的高版本 JDK 移除了部分 API。这类问题的处理顺序是先把 IDE 的项目编译级别切回 1.8再检查 Maven 编译器插件版本如果还报错看是不是用了com.sun.*这种内部 API。最常见的省事方案是直接装一个 JDK 8 来跑别在高版本上死磕。用 IDEA 的话Project Structure 里把 SDK 和 Language Level 都切到 1.8比改代码快得多。6. 进阶把固定批改规则换成你自己的算法源码跑通只是第一步。很多人拿到这套系统后会发现内置的相似度比对算法不适合自己的场景——比如你要批改的是 SQL 作业、要按关键词给分、或者要接一个外部查重接口。这类改造不需要推翻整个系统找准接入点就行。6.1 改造的切入点把批改逻辑抽成接口自动批改模块在代码里的位置通常是一个 Service 方法输入是两份文本或文件路径输出是相似度分数。改造的第一步是把它抽成接口让不同算法实现互相替换。常见做法是public interface GradingStrategy { GradingResult grade(String submitFilePath, Assignment assignment); }然后在实现类里替换算法逻辑。原来的 Jaccard 实现不要删留着做对照新建一个类实现同一个接口通过 Spring 的注入方式切换。这样你的业务层、控制层一行都不用动只换一个 Bean。如果将来要接外部查重服务也只是多写一个实现类的事。核心原则是批改算法是系统里最容易变的部分一定要用接口隔离别把算法写死在 Service 里。6.2 用 Docker 把环境钉死老 Java 项目最大的敌人是环境漂移。同一套源码你机器上跑得好好的换个同事的机器就起不来。我后来习惯用 Docker 把 JDK、Tomcat、MySQL 的版本一次性固定下来免得每次换环境都重新踩一遍坑FROM maven:3.6-jdk-8 AS build COPY . /app WORKDIR /app RUN mvn package -DskipTests再用一个 Tomcat 8.5 的镜像去跑打出来的 war 包MySQL 单独起容器并挂载数据目录。这样整条链路的版本完全一致后面无论换到哪台机器只要 Docker 在环境就不会漂移。从那以后我每次拿到别人的 Java 源码包第一件事不再是 IDE 里点运行而是先看 web.xml、依赖清单和 SQL 脚本把环境链、配置链、数据链钉死再动手。这套方法帮你少走很多弯路希望帮到你。本文还有配套的精品资源点击获取