ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java在线作业提交批改系统:Spring Boot+MyBatis实战详解

Java在线作业提交批改系统:Spring Boot+MyBatis实战详解 简介一套基于Java的在线作业提交与批改系统设计实现面向高校毕业设计、课程设计及Java学习者。系统采用SSM框架围绕管理员、教师、学生三类角色构建管理员统一管理教师与学生账号教师可定义课程、按课程布置作业并设定期限学生在截止前可提交Word作业并支持多次覆盖教师可下载批改、评分并写出评语作业数据支持导出Excel。资源包共454个文件内含Java源码、XML配置、HTML页面、JS/CSS前端资源、SQL数据库脚本以及图片、字体等静态素材目录结构完整整体大小约16.81MB。目前已有212人学习浏览。下载后可直接运行项目查看完整效果也可重点研究SSM分层设计、文件上传下载、在线提交时限控制、数据导出Excel等关键实现为毕业论文或项目面试提供可落地的完整案例参考。1. 在线作业提交批改系统毕业设计选它值不值临近选题季看到“基于JAVA在线作业提交批改系统设计与实现”这个题目很多人第一反应是这不就是一个增删改查的CRUD项目吗等真做起来才发现作业提交涉及文件上传、并发重复提交、权限隔离、批改状态流转每一块都比单纯写接口麻烦得多。这个题目对有一定Java基础、想用Spring Boot MyBatis做一套完整Web系统的同学很合适能覆盖前后端交互、数据库设计、文件存储和事务控制论文也有明确的业务场景支撑。本文按毕业论文的实现路径把需求边界、技术选型、核心代码和常见坑讲透照着做能省下不少查资料的时间。2. 需求拆解与功能边界先想清楚哪些功能能放进毕业论文2.1 三类核心角色与权限模型学生、教师、管理员做在线作业系统第一件事不是写代码而是想明白谁会用它。这个系统里有三类人学生要查作业、交作业、看批改结果教师要发作业、收作业、批改打分管理员不参与日常教学但要管用户和基础数据。如果按真实学校系统做还得有课程、班级的概念但毕业论文建议把维度压到“用户 作业 提交 批改”四个核心实体上。权限模型我一般用最简单RBAC思路用户表里不放角色的逻辑判断而是用一个role字段区分1学生、2教师、3管理员。拦截器里按接口路径前缀做控制/api/student/**只允许学生访问/api/teacher/**只允许教师访问管理员用/api/admin/**。这样做的好处是代码直白答辩时能讲清楚“基于角色的访问控制”不需要引入Spring Security那套复杂的过滤器链。当然如果你对框架熟用Spring Security加注解也行但要让评委看到你理解“为什么要控制权限”而不是背配置。权限要细化到数据层面。学生只能看到自己提交的作业教师只能看到自己布置的作业管理员可以看全局。这一步在SQL里带条件比如WHERE submission.student_id #{userId}不要在代码里查出全部再过滤。很多人在这里翻车控制器里只做了登录校验没做资源归属校验结果学生A能改自己的提交记录成学生B的这在答辩时是硬伤。2.2 作业提交与批改的业务流程状态机与异常路径这个系统的核心不是增删改查而是状态流转。一份作业从发布到归档要经历教师发布作业状态为“收集中”、学生在截止时间前提交生成提交记录、教师批改填入分数和评语、学生查看成绩。对应到数据库我会在提交表里加一个status字段0表示已提交待批改1表示已批改2表示已打回。打回是什么意思教师认为作业不合格学生需要修改后重新提交这是作业系统最常见的异常路径。状态机听起来高大上其实就是if判断。但要注意每个状态转移都要校验前置条件。学生提交时先查homework.status是否允许提交再查submission表里有没有记录——有记录说明是重复提交应该做“更新附件”还是“拒绝操作”我建议给一个resubmit字段控制教师布置作业时设置是否允许重复提交允许的话学生再次提交会覆盖旧附件不允许的话直接返回“该作业已提交无法重复提交”。另外还要处理截止时间超过deadline要拒绝上传这是很多系统的隐形需求。异常路径里最容易忽略的是“教师批改时学生同时提交”。如果不加锁会出现教师读了旧的提交记录打分学生上传了新文件最后数据库里批改成绩对应的文件路径和提交表不一致。解决方式有两种批改时更新提交表状态并通过乐观锁UPDATE submission SET status 1 WHERE id #{id} AND status 0判断行数或者在批改前检查状态不是待批改状态就禁止打分。这里建议用乐观锁代码开销小也适合后面讲技术难点。2.3 功能范围取舍为什么别贪多找毕设题目时很容易看到类似系统里挂了在线聊天、班级论坛、消息通知、作业查重。但论文题目只有“在线作业提交批改系统”你把这些全做进去工作量翻倍每个功能都浅尝辄止反而分不清主次。我做这个题目时第一个决定就是砍需求只保留登录注册、作业管理、在线提交、文件查看、批改打分、成绩列表六个模块。我的取舍原则是“每个模块都要有技术侧重点”。登录注册可以讲JWT鉴权作业提交可以讲文件上传与并发控制批改打分可以讲事务回滚成绩列表可以讲MyBatis动态SQL和排序。查重这种功能涉及算法和文本处理需要在论文里单独花大篇幅不是不行而是会让系统设计变臃肿。如果后续想加我建议把查重做成独立工具类先算“字符重合率”而不是“语义相似度”否则查重本身就要做一个项目。功能范围定了数据库表结构就不会乱。还有一点要提前想清楚要不要支持作业附件批量下载如果要就得在文件列表接口里同时返回文件路径和文件大小前端做循环下载如果只是单个预览则不用加。这个细节影响文件表的字段设计我放在下一章讲。3. 技术选型与数据库设计用 Spring Boot MyBatis 落地3.1 为什么选 Spring Boot MyBatis 而不是其他组合JAVA生态里做Web系统Spring Boot基本是默认选择。它内置Tomcat简化了配置java -jar直接启动部署时不用装独立容器。MyBatis则是SQL与Java方法映射的持久层框架适合手写SQL、控制查询细节。相比JPA/HibernateMyBatis的SQL是自己写的执行过程黑匣子少出问题时容易排查也方便在论文里展示“使用了动态SQL处理多条件查询”。有人会问为什么不直接用Spring Data JPAJPA确实能少写很多SQL但它自动生成的查询对多表关联和状态更新的控制不够直观尤其在并发提交场景下你需要精确控制UPDATE语句的行数影响MyBatis的update方法返回受影响行数很明确。MyBatis-Plus虽然更好用但毕业设计尽量别依赖太多代码生成器评委可能会问“这个BaseMapper是哪来的”你解释起来要清楚框架原理如果解释不清反而减分。前端技术看你的侧重点。如果重点在后端用服务端渲染的Thymeleaf最稳妥如果想让项目看起来完整可以配Bootstrap或Vue前后端分离。我做的时候选了Spring Boot MyBatis做后端接口前端用Thymeleaf加少量原生JavaScript因为这样只需要打一个包部署简单答辩时直接从浏览器演示不用额外启动前端脚手架。如果你的题目明确写了“采用前后端分离”再考虑Vue否则别给自己增加跨域和Token存储的额外工作。3.2 数据库表设计用户、作业、提交、批改四张核心表表设计是整个系统的地基。我见过很多同学上来就建十几张表关系图画得密密麻麻结果实现时一半用不上。核心四张表就够了用户表user、作业表homework、提交表submission、批改记录表review。管理员可以用user表加角色字段区分不需要单独建admin表。下面是我实际用过的表结构字段做了精简-- 用户表 CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, real_name varchar(50) DEFAULT NULL, role tinyint NOT NULL COMMENT 1学生 2教师 3管理员, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 作业表 CREATE TABLE homework ( id int NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL, teacher_id int NOT NULL, deadline datetime NOT NULL, allow_resubmit tinyint DEFAULT 0, status tinyint DEFAULT 0 COMMENT 0收集中 1已截止 2已关闭, description text, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_teacher (teacher_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 提交表 CREATE TABLE submission ( id int NOT NULL AUTO_INCREMENT, homework_id int NOT NULL, student_id int NOT NULL, file_name varchar(255) NOT NULL, file_path varchar(255) NOT NULL, file_size bigint DEFAULT NULL, status tinyint DEFAULT 0 COMMENT 0待批改 1已批改 2已打回, submit_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_homework_student (homework_id,student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 批改记录表 CREATE TABLE review ( id int NOT NULL AUTO_INCREMENT, submission_id int NOT NULL, teacher_id int NOT NULL, score int DEFAULT NULL, comment text, review_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_submission (submission_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个设计细节值得说。submission表用了联合唯一键uk_homework_student保证一个学生对于某次作业最多只产生一条主记录重复提交是更新这条记录而不是插入新记录。file_path存的是相对路径不要存C:/uploads/xxx这种绝对路径否则换机器部署就全乱套。status字段用数字枚举而不是字符串节省空间且查询快在Java代码里定义常量类对应避免魔法数字散落各处。为什么批改单独建一张review表而不是直接update进submission因为如果允许多次批改或打回再批改每次的分数和评语都是历史痕迹单独建表能保住批改历史。论文里可以写“采用批改记录表记录教师操作日志保证可追溯”。这里也呼应了状态机的设计submission.status记录当前状态review记录每次批改动作。3.3 文件存储方案本地目录与数据库元数据如何配合在线作业里最麻烦的是文件。学生传上来的作业可能是Word、PDF、Zip、图片你不能只把文件名存进数据库文件本体要落在一个地方。最省事的方案是存本地磁盘项目根目录下建一个upload/文件夹按作业ID分子目录例如upload/2025/03/21/。数据库file_path保存的是相对路径访问时通过接口做权限校验后返回文件流而不是直接把路径暴露给前端。为什么不推荐用OSS或云存储对于毕设项目本地部署和演示是最重要的云存储还要配AccessKey、同步上传、外网访问安全策略写论文时也要花篇幅介绍成本高于收益。如果你是部署到服务器演示本地磁盘存储同样够用注意定期清理备份即可。配置上我在application.yml里加一个自定义参数upload: path: ./upload读取时用Value(${upload.path})注入。生成文件名不要用原始文件名因为学生上传时可能存在中文、空格、特殊字符很容易引发乱码或路径解析错误。常见做法是文件名重命名为UUID 原扩展名原始文件名单独存进file_name字段用于前端展示。这样磁盘上的文件不会出现非ASCII字符数据库里的中文也安全。文件大小限制可以在Spring Boot的配置里设置spring: servlet: multipart: max-file-size: 20MB max-request-size: 20MB超过大小会抛出MaxUploadSizeExceededException需要在全局异常处理器里捕获返回“文件不能超过20MB”之类的提示而不是把堆栈信息甩给前端。4. 核心功能实现从登录到批改的可运行代码4.1 搭建工程与统一返回结构用Spring Initializr生成项目时依赖选择Spring Web、MyBatis、MySQL Driver和Validation。如果你用Thymeleaf可以再加上。生成后第一件事不是写Controller而是定义统一返回结构让所有接口的返回值格式一致。我习惯用一个ResultT类public class ResultT { private int code; // 200成功 400业务错误 401未登录 500异常 private String message; private T data; public static T ResultT success(T data) { return new Result(200, success, data); } public static T ResultT error(int code, String message) { return new Result(code, message, null); } }逻辑说明这个类把接口返回值包装成JSON前端不需要解析一堆状态码字段。业务处理中错误用Result.error(400, 作业已截止)返回到前端系统异常由全局异常处理器兜底转为Result.error(500, 服务器内部错误)避免泄露异常细节。参数放在三个字段code是给程序判断的message是给人看的data是真正的业务数据。4.2 基于 JWT 的登录鉴权与拦截器登录接口校验用户名密码后生成一个Token返回给前端。Token我用JWT它的好处是服务端不存session扩展性好适合前后端分离。这里给出一个精简的JWT工具类private static final String SECRET your-256-bit-secret; private static final long EXPIRE 24 * 60 * 60 * 1000; // 1天 public static String generateToken(int userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); }参数说明SECRET是签名密钥在真实系统里要用足够长的随机字符串不能写在代码里硬编码但毕设演示可以这样。EXPIRE是过期时间按需求设置为一天或两小时都行。claim(role, role)把角色写进Token拦截器里可以读取省得每次查数据库。注意JWT是无状态的一旦签发无法主动注销所以不要在Token里放敏感信息。拦截器逻辑继承HandlerInterceptor在preHandle里取请求头Authorization去掉Bearer前缀后解析。解析失败返回401解析成功把userId和role放到ThreadLocal或HttpServletRequest属性中供Controller使用。注册拦截器时要排除登录和静态资源路径Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login, /api/auth/register); }这里有个坑注册接口不能被拦截否则学生永远注册不了。而且拦截器只校验Token合法到Controller里还要根据角色判断是否有操作权限比如学生调用教师批改接口即使Token有效也要返回403。4.3 作业提交的接口设计与文件上传参数学生提交作业是最容易出问题的接口。我写Controller时用MultipartFile接收文件再加两个参数homeworkId和studentId。studentId从Token里取不要从前端传否则学生可以伪造提交给别人的作业。代码如下PostMapping(/api/student/submit) public ResultVoid submit(RequestParam(file) MultipartFile file, RequestParam(homeworkId) Integer homeworkId, RequestHeader(Authorization) String token) { int studentId JwtUtil.getUserId(token); return submissionService.submit(homeworkId, studentId, file); }Service里要做的事按顺序是查作业是否存在且未截止、查学生是否已提交且是否允许重交、保存文件到磁盘、插入或更新提交记录。核心逻辑Transactional public void submit(int homeworkId, int studentId, MultipartFile file) { Homework homework homeworkMapper.findById(homeworkId); if (homework null || homework.getStatus() 2) { throw new BizException(作业不存在或已关闭); } if (new Date().after(homework.getDeadline())) { throw new BizException(作业已截止); } Submission old submissionMapper.findByHomeworkAndStudent(homeworkId, studentId); if (old ! null old.getStatus() 1 homework.getAllowResubmit() 0) { throw new BizException(该作业已批改无法重复提交); } // 保存文件 String originalName file.getOriginalFilename(); String ext originalName.substring(originalName.lastIndexOf(.)); String storedName UUID.randomUUID().toString().replace(-, ) ext; String relativePath homeWorkId / storedName; file.transferTo(new File(uploadPath / relativePath)); // 更新或插入 if (old null) { Submission s new Submission(); s.setHomeworkId(homeworkId); s.setStudentId(studentId); s.setFileName(originalName); s.setFilePath(relativePath); s.setFileSize(file.getSize()); s.setStatus(0); submissionMapper.insert(s); } else { old.setFileName(originalName); old.setFilePath(relativePath); old.setFileSize(file.getSize()); old.setStatus(0); submissionMapper.update(old); } }逻辑说明Transactional保证如果数据库操作失败文件保存操作也不能留下垃圾虽然文件系统不受事务控制但至少不会出现“有提交记录但没有文件”的情况。findByHomeworkAndStudent返回旧记录配合联合唯一键天然过滤重复插入。allowResubmit控制在已批改状态下是否允许覆盖避免学生拿已出分的作业反复刷分。这里要特别强调file.transferTo底层是NIO移动文件太小可能不会真正落盘所以测试时一定要传大于1KB的文件否则可能走内存流。我遇到过测试时文件保存成功但磁盘上找不到的情况就是这个原因。4.4 教师批改与打分事务控制与状态同步教师端批改接口接收submissionId、score、comment。批改不是简单insert到review表还要同步更新submission.status。我的实现方式是先查提交记录判断是否处于待批改状态然后插入批改记录最后用乐观锁更新提交状态Transactional public void review(int submissionId, int teacherId, int score, String comment) { Submission submission submissionMapper.findById(submissionId); if (submission null) { throw new BizException(提交记录不存在); } if (submission.getStatus() ! 0) { throw new BizException(该作业已批改过); } Review review new Review(); review.setSubmissionId(submissionId); review.setTeacherId(teacherId); review.setScore(score); review.setComment(comment); reviewMapper.insert(review); // 乐观锁更新status从0改为1 int updated submissionMapper.updateStatusWithLock(submissionId, 0, 1); if (updated 0) { throw new BizException(批改状态冲突请刷新后重试); } }参数说明score我限制为0到100的整数可以在Controller层加注解Valid配合Range校验。updateStatusWithLock对应的SQL是UPDATE submission SET status #{newStatus} WHERE id #{id} AND status #{expectStatus}这样有两个老师同时批改同一条提交时只会有一个更新成功另一个抛异常回滚保证批改记录不会重复写入。如果面试或答辩被问到“怎么防止重复打分”这就是答案。打回操作类似教师填写评语把submission.status改为2。学生端再次提交时走4.3的重复提交分支状态重置为0等待重新批改。整个闭环就通了。5. 避坑指南文件上传、并发提交与中文乱码的五个常见问题5.1 上传文件丢失或超过限制现象学生提交成功但教师端下载文件时404或者上传接口直接报MaxUploadSizeExceededException。排查时发现文件保存的路径和配置的upload.path不一致。原因file.transferTo如果目标目录不存在不会主动创建。另外不同操作系统的路径分隔符处理不好Windows下/和\混用会导致路径失效。上传大小配置只设了max-file-size没有设max-request-size多个文件时总大小超出限制。解决在Application类里加一个CommandLineRunner启动时创建new File(uploadPath).mkdirs()。所有文件路径拼接统一用Paths.get(uploadPath, relativePath)不要手动拼字符串。配置里把max-file-size和max-request-size同时设置并在全局异常处理器里单独捕获该异常返回友好提示。5.2 学生重复提交导致状态错乱现象学生先提交了一次教师还没批改学生再次提交结果系统里出现了两条提交记录或者旧记录被覆盖后status从待批改变成已批改但没有任何批改分数。原因提交接口没有做幂等处理。联合唯一键虽然能挡住重复插入但如果先查再插两个并发请求都可能查到“不存在”然后都执行insert第二个insert抛出DuplicateKeyException程序没捕获就变成了500。解决不要依赖“先查询再插入”作为唯一防线。捕获DuplicateKeyException再查一次并走更新逻辑。更稳妥的做法是在插入SQL中使用ON DUPLICATE KEY UPDATE但MyBatis写这种MySQL特有语法时需要加useAffectedRows配置。毕设里我建议用try { insert } catch (DuplicateKeyException e) { update }代码直白答辩也能讲清楚。5.3 中文文件名乱码现象下载文件时文件名变成一堆问号或者保存到数据库后查出来是乱码。原因Tomcat的编码默认是ISO-8859-1前端上传文件名经过request.getHeader等途径如果没有正确解码就会出现乱码。另外MySQL表字符集没设utf8mb4导致中文字段存储异常。解决在application.yml里设置server.servlet.encoding.forcetrue和force-responsetrue。数据库连接URL加上characterEncodingutf8且建表时用DEFAULT CHARSETutf8mb4。前端下载时用URLEncoder.encode(fileName, UTF-8)前端再用decodeURIComponent解码这是最兼容HTML5下载的写法。血泪经验不要在数据库里直接存中文文件名而是保存原始文件名和磁盘文件名两个字段磁盘文件名统一UUID彻底绕开乱码问题。5.4 批改后作业状态不同步现象教师批改成功review表有分数但学生端看到的作业状态仍是“待批改”。这是因为更新submission.status的SQL没生效问题通常出在MyBatis更新时字段映射不上。原因实体类属性名和表字段名不对应。比如表里submit_time实体里写submitTimeMyBatis开启驼峰映射后没问题但如果你手写了resultMap漏掉了status字段的映射那么执行update set status 1时虽然数据库变了但查询时返回的status仍是默认值0。解决检查Mapper的resultMap确保所有字段都有映射。更省事的方式是在application.yml里开启mybatis.configuration.map-underscore-to-camel-casetrue删除手写的resultMap让MyBatis自动映射。验证方法很简单打印submission.getStatus()看批改后是不是1。5.5 部署后无法访问上传文件现象本地运行一切正常部署到Linux服务器后教师点击查看学生作业附件浏览器返回404。检查服务器上文件存在路径也对。原因部署路径和运行路径不一致。很多同学用绝对路径写死成C:/upload部署后自然找不到或者代码里使用的是相对路径但java -jar运行时的工作目录和预期不同导致./upload实际指向项目外的某个位置。解决建议在启动脚本里显式指定工作目录或者在配置文件里使用可根据jar位置动态计算的路径。一个笨但有效的办法是Value(${upload.path}) private String uploadPath; PostConstruct public void init() { if (!Path.of(uploadPath).isAbsolute()) { uploadPath Path.of(System.getProperty(user.dir), uploadPath).toString(); } }这样无论在哪里启动路径都会基于当前工作目录解析。部署后用find / -name *.docx确认文件实际落在哪里再决定调整配置还是调整代码。这类问题属于典型的“环境差异”不是逻辑错误但最容易让人翻车到深夜。6. 验证与进阶用 JUnit 和 Postman 证明论文里写的每一步能跑6.1 接口级测试Postman 集合与断言写完接口别急着写论文先用Postman把核心流程跑通。我建议建一个测试集合按顺序存好登录、布置作业、提交、批改、查看成绩五个请求。登录接口手动填一次用户名密码把返回的Token用Test脚本自动写入环境变量const data pm.response.json(); pm.environment.set(token, Bearer data.data.token);后续请求在Header中引用{{token}}避免每次手填。提交作业时用form-data类型文件字段选择本地的一个测试PDF接口返回后检查Result.code等于200。这个集合本身就是论文“系统测试”章节的素材截图时用Postman的Runner跑一遍比在浏览器里手工演示专业得多。6.2 业务层测试JUnit 内存数据库为了在论文里写“本系统采用单元测试保障核心业务逻辑”至少要写几个Service层测试。场景是测试提交后状态是否正确、截止时间后是否还能提交、重复提交是否被拦截。测试不能用真实MySQL否则污染库我通常用H2数据库来代替MyBatis的SQL兼容性基本没问题。给一个提交测试的示例SpringBootTest Transactional class SubmissionServiceTest { Autowired private SubmissionService submissionService; Test void submit_validHomework_shouldSaveFileAndSetStatus() { MultipartFile file new MockMultipartFile(file, test.pdf, application/pdf, new byte[1024]); submissionService.submit(1, 1, file); Submission s submissionMapper.findByHomeworkAndStudent(1, 1); assertNotNull(s); assertEquals(0, s.getStatus()); } }这里用Transactional让每个测试回滚不会留下垃圾数据。注意MockMultipartFile的字节数组至少要1024字节不然在H2环境下不会触发磁盘写入逻辑。测试通过后再跑一遍整个项目确认没有“测试时没启动Redis/MySQL”的依赖问题。6.3 性能与安全验证并发提交与越权访问检查答辩时常见问题就是“你这个系统能扛多少人同时提交”。你不需要真的用JMeter压出数据但可以结合代码做说明提交接口加了幂等控制批改接口用乐观锁文件存储走本地IO瓶颈主要在数据库连接和磁盘写入。如果要写实证可以用一个简单的并发测试Test public void concurrentSubmit() throws Exception { ExecutorService pool Executors.newFixedThreadPool(10); CountDownLatch latch new CountDownLatch(10); for (int i 0; i 10; i) { pool.execute(() - { try { submissionService.submit(1, 1, file); } catch (Exception ignored) {} latch.countDown(); }); } latch.await(); ListSubmission list submissionMapper.findByHomeworkId(1); // 断言只有一条记录或两条取决于是否允许重交 }并发提交时联合唯一键和乐观锁会拦住重复插入或重复批改最终数据一致。把这个测试代码写进论文“系统性能与并发安全”一节属于实践出真章评委会认为你考虑了真实场景。越权检查更简单用学生Token访问/api/teacher/review接口断言返回403或业务错误。这个测试可以在拦截器层面做也可以在Controller层传参时校验角色。写代码时就把角色校验写在拦截器测试时只需要改变Token的role载荷即可。6.4 给毕业论文答辩的演示脚本最后建议准备一个“演示脚本”控制在10分钟以内。打开项目前先把数据库重新导入一份干净的数据里面预置一个教师账号、两个学生账号一个已提交未批改的作业、一个已批改的作业。演示顺序是学生登录提交作业→教师登录看到待批改列表→教师打分并写评语→学生刷新看成绩和评语。核心讲三个点一是文件上传后如何存储二是状态如何流转三是重复提交和并发批改时如何保证一致。如果时间充裕再演示一下Postman里的Runner说明你做了自动化接口测试。我习惯在答辩前把测试代码跑一遍并截图保存在论文“测试”章节里防止现场环境出问题。这个项目的坑我都踩过一遍尤其中文文件名和文件路径这两个几乎是每个做在线作业系统的人都会碰到的关卡。把这些经验写在论文的FAQ或避坑部分反而比堆功能更显真实。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进