ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Servlet+JSP少儿编程网站源码拆解:三层架构与自动判分实现

Servlet+JSP少儿编程网站源码拆解:三层架构与自动判分实现 简介基于Java Web的少儿编程网站源码采用MVC开发模式且不依赖任何框架适合正在学习服务端编程与MySQL数据库交互的初学者也可作为毕业设计或课程设计的完整参考。压缩包共包含七十一个文件以Java源文件、JSP页面、Servlet类、CSS样式和PNG图片为主其中十二个JSP页面实现了用户注册登录、笔记管理和反馈管理等核心界面十四个Java类对应后台业务逻辑与数据操作七个CSS文件负责页面样式另有E-R图和流程示意图辅助理解数据库结构资源包整体仅三点六九MB。目前已有六十七人浏览学习属于轻量但结构完整的Java Web入门项目。解压之后可获取完整网站源码、数据库设计图以及说明文档能直观查看用户管理、笔记管理、反馈管理三大模块的前后端实现对理解传统分层开发流程和完成课程设计都很有帮助。1. 拆完这份 Java Web 少儿编程网站源码它能跑通的不只是课程展示做少儿编程培训最累的往往不是上课而是作业闭环。我见过太多老师在微信群里收代码截图、用 Excel 登记成绩、再手动判对错十几个学员还能忍几十个就彻底失控。这份「基于 Java Web 的少儿编程网站.zip」源码包我拆完之后判断它最大的价值不是页面多漂亮而是把“学生看课 → 提交编程练习 → 服务端自动判分 → 教师看统计”这一整条链路跑通了。它适合 Java Web 方向的计算机专业学生做课程设计参考也适合机构老师拿回来改一改当内部教学后台。登录进来的角色有学生、教师、管理员三种课程、练习题、提交记录都有对应的表结构和页面。接下来我从数据表开始拆一直拆到判分进程和常见翻车点。2. 三层架构与数据表Servlet JSP 的经典骨架要先立住2.1 技术选型Servlet JSP 为什么是这套源码的正确起点一提到 Java Web很多人的第一反应是 Spring Boot。但这套源码用的偏偏是最传统的 Servlet JSP配 JDBC 直连数据库。我的判断是这种选型在课程设计和教学系统场景里其实是优点。Servlet 的请求入口一眼就能看懂一个路径对应一个类接手的时候不用去翻一大堆自动配置。对计算机专业的学生来说Controller 层是透明的不会出现“项目能跑但不知道为什么能跑”的情况。它当然不是生产级方案。高并发撑不住、鉴权模型偏简单、密码加密还停留在 MD5 加盐级别。但作为教学后台或课设源码这套链路是完整的改造成 Spring Boot 也不难我在第 6 章会给出迁移路径。你先别急着替换框架第一步是把 Servlet JSP 的请求流转看懂后面所有坑都好定位。2.2 包结构与请求入口拿到源码从哪里看起拿到 zip 解压后我一般先不看页面直接打开src/main/java下的包结构。常见的分层是三段式controller放 Servletservice放业务逻辑dao放 JDBC 操作再加一个filter包处理登录拦截util包放 MD5、DBUtil 这类工具。实体类放在entity包对应数据库表字段。请求入口有两种写法需要先确认web.xml里配servlet-mapping或者 Servlet 3.0 之后直接在类上写WebServlet注解。教学类源码现在大多用注解省得 web.xml 越写越长。下面这段是登录入口的典型写法WebServlet(/user/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 真正逻辑在 service 层Servlet 只做参数收口和页面跳转 String username req.getParameter(username); String password req.getParameter(password); User user userService.login(username, password); if (user null) { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /course/list); } }这段代码的逻辑很直白从表单拿参数交给userService.login()做数据库校验成功就把用户对象塞进 Session失败则带着错误信息转发回登录页。注意resp.sendRedirect()后面跟的是项目上下文路径req.getContextPath()不是裸路径/course/list否则部署到带 context 的 Tomcat 下会 404。我习惯把源码里的请求路径先列成一张路由表对照着看功能边界。这份源码里常见路径是下面这些请求路径功能可访问角色/user/register学生注册公开/user/login登录公开/course/list课程列表登录用户/exercise/detail练习题详情登录用户/submission/submit提交代码学生/teacher/exercise/add添加练习题教师/admin/user/list用户与班级管理管理员对照这张表再去看 Filter 的拦截范围就清楚为什么静态资源会被误伤——这个问题我在第 5 章会单独讲。2.3 数据库设计五张核心表与字段的含义这套源码的数据库设计是典型的教育管理系统结构核心表就那么几张。先把导入数据库的脚本看清楚再动手跑项目。下面这段建表 SQL 是我按源码常见结构整理的你可以直接对照CREATE DATABASE IF NOT EXISTS kids_edu DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE kids_edu; -- 班级表教师开课和学生分班都依赖它 CREATE TABLE tb_class ( class_id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(50) NOT NULL COMMENT 班级名称如周六上午班 ); -- 用户表学生、教师、管理员用 role 字段区分 CREATE TABLE tb_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL COMMENT MD5加盐后的密文, real_name VARCHAR(50), role TINYINT NOT NULL DEFAULT 1 COMMENT 1学生 2教师 3管理员, class_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (class_id) REFERENCES tb_class(class_id) ); -- 课程表一门课对应一位教师 CREATE TABLE tb_course ( course_id INT PRIMARY KEY AUTO_INCREMENT, teacher_id INT NOT NULL, title VARCHAR(100) NOT NULL, summary TEXT, cover_path VARCHAR(255) COMMENT 封面图相对路径, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (teacher_id) REFERENCES tb_user(user_id) ); -- 课时表一门课包含多个课时课件和视频都挂在课时下 CREATE TABLE tb_lesson ( lesson_id INT PRIMARY KEY AUTO_INCREMENT, course_id INT NOT NULL, title VARCHAR(100) NOT NULL, content_path VARCHAR(255) COMMENT 课件文件相对路径例如 upload/lesson/xxx.pdf, video_url VARCHAR(255), sort_no INT DEFAULT 0, FOREIGN KEY (course_id) REFERENCES tb_course(course_id) ); -- 练习题表expected_output 是自动判分的比对基准 CREATE TABLE tb_exercise ( exercise_id INT PRIMARY KEY AUTO_INCREMENT, lesson_id INT NOT NULL, title VARCHAR(100) NOT NULL, description TEXT COMMENT 题目描述可带积木块示意, starter_code TEXT COMMENT 初始代码学生在此基础上补全, expected_output TEXT COMMENT 预期输出判分时逐行比对, time_limit_sec INT DEFAULT 5, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (lesson_id) REFERENCES tb_lesson(lesson_id) ); -- 提交记录表每次判分的结果都留痕 CREATE TABLE tb_submission ( submission_id INT PRIMARY KEY AUTO_INCREMENT, exercise_id INT NOT NULL, student_id INT NOT NULL, code_content TEXT, status TINYINT DEFAULT 0 COMMENT 0未判 1通过 2失败, judge_output TEXT COMMENT 实际运行输出或编译报错, score INT DEFAULT 0, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (exercise_id) REFERENCES tb_exercise(exercise_id), FOREIGN KEY (student_id) REFERENCES tb_user(user_id) );注意这里有个关键选择用户表用tb_user而不是user。因为user在 MySQL 里是保留字直接建表会触发语法歧义很多新手导入源码 SQL 时报错根因就在这。判分核心在tb_exercise.expected_output字段——它存的是这道题预期的控制台输出文本学生提交的 Java 代码运行后服务端把实际输出和它做逐行比对一致就判通过。2.4 数据库连接配置db.properties 里的五个参数源码里数据库连接信息一般集中在src/main/resources/db.properties。拿到源码后第一件事就是改这里改错任何一项项目都起不来。下面是我整理的对照模板# database connection jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/kids_edu?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.pool.maxActive10jdbc.driver这个值最容易翻车。MySQL 5.x 时代写com.mysql.jdbc.DriverMySQL 8.x 以后必须写成com.mysql.cj.jdbc.Driver否则启动直接报ClassNotFoundException。serverTimezoneAsia/Shanghai是 MySQL 8 连接时的必备参数不加它会报时区错误这是新手踩得最多的一处配置问题。改完配置文件我会先用命令行验证数据库脚本有没有跑干净再启动 Tomcatmysql -u root -p db_init.sql如果 SQL 文件里没有USE kids_edu;导入时会默认进入当前库表会建错位置登录时表现为“表不存在”。这里建议打开 Navicat 或命令行确认kids_edu库下确实有tb_user这张表再做下一步。3. 登录、权限与课程列表把请求链路一层层看明白3.1 注册与登录从表单到 Session 的一次完整流转登录功能是这套源码的入口也是大多数课程设计答辩必问的地方。它的流转路径是login.jsp表单提交到LoginServletServlet 调UserService.login()Service 再走 DAO 层执行 PreparedStatement 查询用户成功就写 Session失败就回登录页。我平时给同事讲这段代码会重点强调两个细节。第一个细节是防 SQL 注入。源码里如果不小心用了字符串拼接 SQL登录框输入 or 11就能直接绕过密码。正确做法是像下面这样用PreparedStatement的参数占位符public User login(String username, String md5Password) { String sql SELECT user_id, username, real_name, role, class_id FROM tb_user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setUserId(rs.getInt(user_id)); u.setUsername(rs.getString(username)); u.setRealName(rs.getString(real_name)); u.setRole(rs.getInt(role)); u.setClassId(rs.getInt(class_id)); return u; } } } catch (SQLException e) { e.printStackTrace(); } return null; }第二个细节是密码不能以明文形式进数据库。这套源码里如果用的是 MD5 加盐md5Password就是MD5Util.md5(rawPassword SALT)的结果SALT可以定义在工具类里。课程设计阶段这样做可以接受但如果要放到真实机构里跑我建议把 MD5 替换成 BCryptUserService里做BCrypt.checkpw()校验改动很小但安全等级完全不同。3.2 登录拦截Filter 只拦业务路径别碰静态资源登录功能写完后必须有一个 Filter 挡住未登录用户否则任何同学都能直接访问课程详情和提交链接。这套源码里典型的拦截器写法是对业务路径做前缀匹配。我看过不少翻车案例问题都出在 Filter 配了/*导致 CSS、JS、图片全被拦住页面打开是裸 HTML。正确的匹配范围应该是下面这样WebFilter({/course/*, /exercise/*, /submission/*, /teacher/*}) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpSession session req.getSession(false); if (session null || session.getAttribute(loginUser) null) { ((HttpServletResponse) response).sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }注意req.getSession(false)这里必须传false意思是“拿不到 Session 就返回 null而不是新建一个”。如果写成req.getSession()那么每次请求都会强制创建 SessionFilter 的判断就永远失守了未登录用户也能一路畅通。这里的chain.doFilter()是关键动作它表示放行。前面sendRedirect()跳转登录页时要带上req.getContextPath()否则部署在/kidsweb上下文下重定向会变成localhost:8080/login.jsp照样 404。请求路径匹配、Session 判空、放行这三个动作缺一个登录拦截就是摆设。3.3 课程列表与详情Service 返回数据JSP 负责拼页面页面渲染这部分源码用的是 JSP JSTL。Service 层把课程列表查出来塞进 request转发给 JSPJSP 再用c:forEach循环输出卡片。理解这个流程的关键在于区分request.setAttribute()和 JSP 里的取值方式。下面这段是课程列表页里的典型片段%-- courseList.jsp --% c:forEach items${courseList} varcourse div classcourse-card h3a href${pageContext.request.contextPath}/course/detail?courseId${course.courseId}${course.title}/a/h3 p${course.summary}/p span讲师${course.teacherName}/span /div /c:forEach${courseList}是 EL 表达式它取值来自request.getAttribute(courseList)。很多同学容易混淆的点是从detail?courseId1这种链接拿参数要用param.courseId而拿到数据库查询结果要用setAttribute getAttribute这条线两者不是一个存储位置。页面没数据时先确认 Service 是否把 list 放进 request再确认 JSP 里 EL 的变量名拼写一致。这里还有一个容易被忽略的点课程封面、课件路径都是相对路径JSP 里拼接时必须带${pageContext.request.contextPath}。如果不带页面部署到根路径时没问题部署到带上下文的路径下就全部指向错了这是部署类源码最常见的资源 404 问题。4. 练习题与自动判分从提交代码到比对输出的完整实现4.1 练习题如何组织题目、起始代码与预期输出少儿编程教学里的常见做法是“积木拖拽 代码过渡”。低龄段用 Scratch 拖积木到小学高年级和初中转入代码训练这套源码里练习题的定位就是后者面向图形化到代码的过渡阶段题目是 Java 控制台小程序比如打印九九乘法表、计算求和、判断奇偶数之类。每个练习题挂在课时下由tb_exercise表承载。description字段放题目要求和示例starter_code是学生看到的初始代码通常留一个空方法expected_output存标准答案的控制台输出。判分逻辑就是“运行学生提交的完整代码 → 拿到 stdout 文本 → 和 expected_output 比对”。这种方案对入门题足够用但对格式敏感只要多一个空格、多一个换行就可能误判所以后面必须做输出归一化。我一般会让题目描述里附带“预期输出格式”区块明确告诉学生输出的行数、分隔符和大小写。这一条能少掉一半判分争议。源码里如果已经有类似的提示模板保留并且统一它。4.2 自动判分用 ProcessBuilder 跑用户代码并管好子进程判分是整个源码里最有技术含量的一环。它的本质是在服务端启动一个子进程编译并运行学生提交的 Java 代码然后捕获输出。这里有个巨大的坑——如果直接Runtime.getRuntime().exec(java Main)而不做超时和目录隔离一个死循环代码就能拖垮整个 Tomcat。我习惯的判分核心代码长这样String judgeDir /tmp/kidsweb/judge/u_ userId /e_ exerciseId; Files.createDirectories(Paths.get(judgeDir)); // 把学生代码写入独立临时目录避免污染项目路径 Files.write(Paths.get(judgeDir, Main.java), codeContent.getBytes(StandardCharsets.UTF_8)); // 编译阶段编码必须显式指定 UTF-8否则 Windows 下中文输出全乱码 ProcessBuilder compilePb new ProcessBuilder(javac, -encoding, UTF-8, Main.java); compilePb.directory(new File(judgeDir)); compilePb.redirectErrorStream(true); Process compileProc compilePb.start(); boolean compileDone compileProc.waitFor(10, TimeUnit.SECONDS); if (!compileDone) { compileProc.destroyForcibly(); submission.setStatus(2); // 编译超时 } else if (compileProc.exitValue() ! 0) { String compileErr new String(compileProc.getInputStream().readAllBytes(), StandardCharsets.UTF_8); submission.setJudgeOutput(compileErr); submission.setStatus(2); } else { // 运行阶段等待时间受题目的 time_limit_sec 控制 ProcessBuilder runPb new ProcessBuilder(java, Main); runPb.directory(new File(judgeDir)); Process runProc runPb.start(); boolean finished runProc.waitFor(exercise.getTimeLimitSec(), TimeUnit.SECONDS); if (!finished) { runProc.destroyForcibly(); submission.setStatus(2); submission.setJudgeOutput(TIME_LIMIT_EXCEEDED); } else { String output new String(runProc.getInputStream().readAllBytes(), StandardCharsets.UTF_8); submission.setJudgeOutput(output); boolean ok normalize(output).equals(normalize(exercise.getExpectedOutput())); submission.setStatus(ok ? 1 : 2); submission.setScore(ok ? 100 : 0); } } // 最后记得清理临时目录避免磁盘被学生代码塞满这段代码有几个点需要深入说。第一compilePb.directory(new File(judgeDir))是必须的它把子进程的工作目录切到那个独立临时目录这样javac Main.java才会按预期找到文件。第二redirectErrorStream(true)把编译错误和标准输出合并到同一根管道这样读一次就能拿到全部信息否则要同时读 errorStream 和 inputStream代码复杂度会成倍上升。第三waitFor(timeout, TimeUnit.SECONDS)是阻塞等子进程结束但加了超时上限一旦超时就destroyForcibly()否则死循环代码会让子进程越来越多最后服务器卡死。这里我见过一个很隐蔽的问题明明天天跑判分但服务器每隔几天就 CPU 飙升。原因是子进程虽然结束了但部分 JDK 内部守护线程没有完全释放更常见的是从来没有 destroy 编译进程导致一个学生一次提交留下好几个僵尸 java 进程。解决思路是try (…)资源释放写法或者在判分结束时统一 destroy。所有判分结果都回写tb_submission表包括状态码status1 通过2 未通过和实际运行输出。教师端统计页直接从这个表聚合按题号、按班级就能拉出正确率报表。4.3 Scratch 内容嵌入iframe 与积木代码对照的配合这套网站的少儿编程特色体现在课件和练习题展示层。Scratch 3 导出的项目本质是一个 HTML 网页源码里最常见的做法是把它放在webapp/upload/scratch/目录下然后用 iframe 嵌入课时页。下面这种写法在课件页里很普遍div classscratch-player iframe srcupload/scratch/02-小猫走迷宫.html width960 height540 title小猫走迷宫项目 /iframe /diviframe 的src是相对路径部署到 Tomcat 后浏览器会以当前页面的访问路径为基准去拼。课件放在upload/scratch/下页面 URL 是/kidsweb/lesson/detail?lessonId1这时候相对路径upload/scratch/...会解析成/kidsweb/lesson/upload/scratch/...而不是/kidsweb/upload/scratch/...于是就会 404。这是 Scratch 项目嵌不进去最常见的坑解决方式是用 EL 表达式写全路径iframe src${pageContext.request.contextPath}/upload/scratch/02-小猫走迷宫.html .../iframe练习题页面里我还会建议保留一个“积木与代码对照”的展示区。tb_exercise.description里可以放一段 HTML用左右两栏结构左边是积木示意图图片外链或本地图右边是 Java 代码。这一步不涉及复杂逻辑但能显著提升教学体验——学生能看到“今天拖的那些积木翻译成代码长什么样”学习动机比直接抛一段代码强得多。5. 常见问题与避坑五处会影响你复现的关键配置5.1 数据库导入后登录报“表不存在”多半是 user 表名和库名的问题现象SQL 脚本导入成功但一登录就报Table kids_edu.user doesnt exist或者干脆提示数据库里空表一片。原因第一种源码里用户表直接叫user而user是 MySQL 保留字导入时被当成特殊语法处理表没建成或者建错。第二种SQL 脚本开头没有USE kids_edu;表建到了默认库下连接 URL 却指向kids_edu。解决建表语句统一用tb_user这类带前缀的表名导入前确认连接 URL 里的库名和脚本开头的USE一致。我的习惯是导入完成后先执行SHOW TABLES;确认五张核心表都在再启动项目。5.2 Tomcat 版本不对导致 JSP 一直编译失败现象启动 Tomcat 后访问任意 JSP 页面控制台报The superclass javax.servlet.http.HttpServlet was not found on the Java Build Path一堆类找不到页面 500。原因源码是基于javax.servlet.*写的但 Tomcat 10 开始把包名改成了jakarta.servlet.*。如果本机装的是 Tomcat 10源码里的import javax.servlet.http.HttpServlet自然全部失效。解决换回 Tomcat 8.5 或 9.0配合 JDK 8 使用。这不是源码的问题是服务器版本匹配的问题。如果你一定要用 Tomcat 10那要全局替换javax.servlet为jakarta.servlet工作量不小不建议为了一个课程设计项目去折腾。5.3 判分结果是 0 分但代码在本机运行完全正常现象学生在本机 Eclipse 里运行代码输出完全正确提交到网站后判分永远status2打开 judge_output 发现乱码或多余字符。原因两个层面。第一Windows 下javac默认编码是 GBK代码里如果包含中文编译后的字符串在子进程输出时是 GBK 字节而expected_output是从 MySQL 读出来的 UTF-8 文本两者比对必然失败。第二Windows 换行符是\r\nLinux 是\n比对时多一个\r字符。解决编译命令固定加-encoding UTF-8运行参数也显式指定同时比对前做字符串归一化。归一化函数我放在判分工具类里三段代码就能解决private static String normalize(String text) { if (text null) { return ; } // 统一换行符再掐头去尾忽略末尾多余空白 return text.replace(\r\n, \n).replace(\r, \n).trim(); }从那以后我每次判题都强制走一遍normalize不再信任学生代码和数据库文本的原始格式。5.4 登录跳转后 CSS 全丢页面变成裸 HTML现象登录成功后跳转到课程列表页页面结构还在但所有样式、图片全部消失控制台一堆 404。原因Filter 拦截范围配成了/*把/css/main.css、/js/main.js这些静态资源也当成业务请求拦掉了未登录判断触发后直接跳登录页资源请求拿到的是 HTML 而不是 CSS。另一个常见原因是页面里 CSS 路径用的是相对路径部署上下文一变就失效。解决Filter 注解范围收窄到业务前缀只匹配/course/*、/exercise/*这类路径静态资源完全放行。JSP 里静态资源全部改用${pageContext.request.contextPath}/css/main.css拼绝对路径。这两个动作做完CSS 丢失问题基本绝迹。5.5 ZIP 解压后中文文件名乱码课件目录全变成火星文现象zip 解压后upload/lesson/下的附件文件名、scratch/下的 HTML 文件名全部乱码工程编译后页面引用乱码路径课件打不开。原因压缩包生成时的文件名编码和本地系统不一致。Windows 下如果压缩工具没有按 UTF-8 存储文件名解压到中文系统时就会按 GBK 反向解码结果就是一堆乱码文件名。解决用 7-Zip 或 Bandizip 打开 zip解压选项里选择“以 UTF-8 解码文件名”不要直接用 Windows 自带的资源管理器解压。如果已经解压出乱码在压缩软件里重新解压一次即可。检查工程时确认web.xml和 JSP 页面头部都声明了 UTF-8 编码避免二次乱码。6. 扩展改造与验证把这份源码变成你自己的教学后台6.1 跑通后的验证清单一张表确认每个角色都没白给源码拿到手先别急着改代码按角色把主链路完整跑一遍确认项目本身没问题。我整理的验证顺序是下面这张表步骤操作预期结果1导入数据库脚本改 db.properties项目启动无报错2访问/kidsweb/register.jsp注册一个学生账号注册成功跳转登录页3用学生账号登录进入课程列表页样式正常4打开一个课程详情页课件和 iframe 里的 Scratch 项目能加载5进入练习题提交一段正确代码判分通过状态变为 16提交一段故意写错的代码判分失败能看到编译报错或输出比对失败7用教师账号登录新增一道练习题学生端刷出题目8用管理员账号查看用户列表能按班级筛选学生第 5 步是核心如果判分链路能跑通这套源码的主要价值就兑现了。我帮一位做少儿编程教学的老师改过类似项目比对输出这块花的调试时间最长多数不是逻辑问题而是编码和换行符问题。6.2 改成 Spring Boot 的三个动作如果不想在 Servlet JSP 上继续投入可以按三步迁移到 Spring Boot。第一步把WebServlet注解换成ControllerPostMapping第二步把web.xml里的配置换成配置类或 application.yml第三步把 JDBC 模板换成 JdbcTemplate 或 MyBatis。登录逻辑迁移后大概长这样Controller public class LoginController { PostMapping(/user/login) public String login(String username, String password, HttpSession session) { User user userService.login(username, MD5Util.md5(password SALT)); if (user null) { return login; } session.setAttribute(loginUser, user); return redirect:/course/list; } }return login对应的是视图名Spring Boot 会解析到模板目录下的login.htmlredirect:/course/list是重定向语义和 Servlet 里的sendRedirect一致。数据库访问层建议直接换 JdbcTemplate原来几十行 JDBC 样板代码能压到十行以内。6.3 一个更实际的扩展练习截止时间不要只看前端很多课设项目会把截止时间存在前端表单里提交时只在前端 JS 判断。这完全不设防——学生改一下本地时间或直接绕过页面就能在截止后继续提交。我的做法是tb_exercise表加一个deadline字段判分 Servlet 里用服务端时间校验// 前端传的时间不可信必须用数据库里的截止时间和服务端当前时间比对 if (LocalDateTime.now().isAfter(exercise.getDeadline())) { submission.setStatus(3); // 状态码用于标识逾期 submission.setJudgeOutput(已超过截止时间无法提交); // 这里直接 return不再执行编译和运行 }这个改动虽然只有几行但它把判分可靠性提升了一个档次。第一次带着几个学生做课程设计时因为 Filter 配错把整个页面的静态资源拦死我折腾了一下午才定位到问题。从那以后我每次拿到 Java Web 源码都强制走一遍“建库 → 改配置 → 启动 → 用一个测试账号跑通主链路”四个步骤再往下改代码。这份源码的完整链路不长值得花一下午把它吃透。希望帮到你。本文还有配套的精品资源点击获取
返回列表