简介:一套开箱即用的Java Web错题管理项目,后端用Java开发,数据库采用MySQL 5.7+,支持管理员登录、错题录入、查看、编辑、删除、搜索及密码修改等核心功能。压缩包里包含两个可直接执行的SQL脚本(zaixiancuoti.sql和demo.sql),覆盖完整表结构与初始数据;提供标准Maven项目结构,含pom.xml依赖配置、src/main下的Java源码与JSP页面、target编译输出目录、.idea工程配置文件,以及my.log运行日志和归档文件。配套readme.txt详细说明了JDK版本要求(建议1.8)、MySQL环境配置、数据库导入步骤、Tomcat部署流程和常见问题处理方法。demo.xlsx为示例错题数据,方便快速验证功能。整个系统无需二次修改即可在本地IDEA+Tomcat环境中启动运行,适合计算机类专业学生直接用于毕业设计或课程实训。
1. 项目概述:为什么这个错题系统值得你花时间细看
我带过六届计算机专业毕业设计,每年都会收到至少三十份“在线错题本”类选题。其中八成学生卡在同一个地方:不是写不出功能,而是搭不起一个能跑起来的、结构清晰、逻辑自洽、后续还能扩展的Web骨架。很多人从网上随便下个源码,导入IDEA后报一堆红叉——缺依赖、路径错、数据库连不上、JSP编译失败……最后硬着头皮改配置、删文件、降版本,折腾三天,连登录页面都打不开。这套“Java Web错题管理系统”,就是我去年给大四学生做课程实训时,亲手从零重构并沉淀下来的“最小可行教学基线版”。它不炫技,没用Spring Boot自动装配、没上Redis缓存、没搞前后端分离,就用最朴素的Servlet + JSP + JDBC三层结构,但每一步都踩在教学实践的真实痛点上:MySQL脚本开箱即用、IDEA工程配置完整保留、Tomcat部署路径明确到具体war包名、日志输出有归档机制、甚至把学生最容易忽略的字符集问题(中文乱码)和时区配置(MySQL时间戳偏差)都提前埋好了补丁。关键词里写的“Java错题系统”“Web错题管理”“毕业设计源码”,不是虚的——它解决的从来不是“能不能实现增删改查”,而是“能不能在答辩前一周稳定跑通、方便调试、便于讲解架构”。你拿到手的不是一个黑盒压缩包,而是一套可追溯、可验证、可拆解的教学级工程样本。里面没有一行代码是为“看起来高级”而加的,每一处设计都在回答一个问题:学生在实验室电脑上,用JDK 1.8、MySQL 5.7、Tomcat 8.5这些教务处统一安装的旧版本,如何在两小时内完成从环境搭建到功能演示的全流程?这才是它作为“毕业设计源码”的真正价值。
2. 整体架构与技术选型解析:为什么坚持用“老技术栈”
2.1 技术栈组合的底层逻辑:教学场景优先于技术潮流
这套系统的技术选型,乍看有点“复古”:Servlet 3.1 + JSP 2.3 + JDBC + MySQL 5.7 + Tomcat 8.5。有人会问,为什么不直接上Spring Boot?答案很实在:教学可控性 > 开发效率。Spring Boot的自动配置像一层厚厚的奶油,掩盖了Web容器启动、Servlet生命周期、请求转发链路这些必须理解的底层机制。学生在毕业答辩时被问到“请求从浏览器发出,到你页面上显示错题列表,中间经过了哪些关键对象?”,如果只答“Controller调Service再查数据库”,那离及格线还差一截。而用原生Servlet,他必须亲手写web.xml或@WebServlet注解,必须理解HttpServletRequest和HttpServletResponse的流转,必须在doGet()里手动调用DAO层——这个过程本身,就是一次微型的Web原理沙盘推演。JSP也没被淘汰,它在这里承担的是“教学可视化载体”的角色:每个.jsp文件都是一个独立的视图单元,学生可以直观看到HTML结构、Java脚本片段(<% %>)、EL表达式(${})如何混合渲染,比Vue单文件组件更直白地暴露“服务端模板引擎”的本质。至于JDBC,它强迫学生直面SQL语句、PreparedStatement预编译、事务边界这些数据库交互的原始契约,而不是被MyBatis的@Select或Hibernate的HQL温柔包裹。这不是拒绝新工具,而是把“理解基石”放在“使用工具”之前。就像学骑车先练平衡,而不是直接上电助力。
2.2 目录结构设计:每一个文件夹都在传递工程规范意识
压缩包里的目录树,不是随意堆砌的,而是按Maven标准结构做了教学化精简:
zaixiancuoti/ ← 项目根目录(对应Maven groupId:com.example)
├── pom.xml ← 依赖清单,只保留绝对必要项:servlet-api、mysql-connector-java、jstl
├── src/
│ └── main/
│ ├── java/ ← Java源码,严格分包:com.example.dao(数据访问)、com.example.servlet(控制器)、com.example.bean(实体)、com.example.util(工具类)
│ ├── resources/ ← 配置文件,只有db.properties(数据库连接参数),无XML配置污染
│ └── webapp/ ← Web资源根目录
│ ├── WEB-INF/
│ │ ├── web.xml ← Servlet注册中心,定义所有URL映射(如/login → LoginServlet)
│ │ └── lib/ ← 空目录,提醒学生:运行时依赖由Maven管理,此处不放jar
│ ├── css/ ← 样式文件,仅base.css(重置默认样式+基础布局)
│ ├── js/ ← 脚本文件,仅validate.js(前端表单校验,非框架)
│ └── *.jsp ← 所有页面:index.jsp(首页)、login.jsp(登录)、list.jsp(错题列表)等
├── target/ ← 编译输出目录,含classes(字节码)和zaixiancuoti.war(可部署包)
├── log/ ← 日志目录,含my.log(实时日志)和my.log.2024-06-01.gz(按日归档)
├── demo.sql ← 建库建表+初始数据(管理员账号admin/123456,5条示例错题)
├── zaixiancuoti.sql ← 纯建库建表脚本(无INSERT),适合生产环境初始化
├── demo.xlsx ← Excel格式示例数据,字段与数据库表完全对应,方便学生导入练习
└── readme.txt ← 不是说明书,是“避坑指南”:明确写出JDK需1.8(非11或17)、MySQL需5.7+(因用到了JSON类型)、Tomcat需8.5(兼容Servlet 3.1)
这个结构刻意回避了src/test(学生极少写单元测试)、src/main/filters(配置多环境太复杂)、src/main/assembly(打包插件超纲)。它传递的核心信息是:一个Web项目,最小必要组成部分是什么? 学生导入IDEA后,一眼就能看清“代码在哪”“页面在哪”“配置在哪”“数据库怎么连”,这种清晰感,比任何框架文档都管用。
2.3 数据库设计哲学:用最少的表,覆盖最核心的业务流
demo.sql和zaixiancuoti.sql脚本的设计,遵循“够用即止”原则。整个系统只用3张表:
-- 1. 用户表(user):存储管理员账号密码,仅此一用
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(100) NOT NULL COMMENT '密码(MD5加密)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 2. 错题表(question):核心业务表,字段直击痛点
CREATE TABLE `question` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(200) NOT NULL COMMENT '题目标题(如:Java中String和StringBuilder区别)',
`content` TEXT NOT NULL COMMENT '题目内容(含代码片段、公式等)',
`answer` TEXT NOT NULL COMMENT '参考答案',
`category` VARCHAR(50) DEFAULT '其他' COMMENT '分类(Java基础、算法、数据库...)',
`difficulty` TINYINT(4) DEFAULT '2' COMMENT '难度(1-5,数字越大越难)',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 3. 操作日志表(operation_log):非业务必需,但教学极有价值
CREATE TABLE `operation_log` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`operator` VARCHAR(50) NOT NULL COMMENT '操作人(用户名)',
`action` VARCHAR(100) NOT NULL COMMENT '操作动作(新增错题、修改错题、删除错题)',
`target_id` INT(11) DEFAULT NULL COMMENT '关联错题ID(如删除操作则记录被删ID)',
`ip_address` VARCHAR(50) DEFAULT NULL COMMENT '操作IP(用于演示request.getRemoteAddr())',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
为什么只有这3张?因为毕业设计答辩时,评委最常问:“你的数据库设计依据是什么?”——答案必须是“业务需求驱动”,而非“看着别人项目抄”。user表支撑登录认证;question表承载全部错题管理功能,category和difficulty字段预留了未来按分类/难度筛选的接口;operation_log表看似冗余,实则是绝佳的教学案例:它让学生亲手实践“如何在DAO层插入日志”“如何用request.getRemoteAddr()获取客户端IP”“如何处理DATETIME类型的自动更新”。没有用户角色表(RBAC)、没有标签表(Tag)、没有收藏表(Favorite),因为那些会让学生的ER图瞬间复杂化,偏离“掌握核心流程”的主线。这种克制,恰恰是成熟工程师的标志。
3. 核心功能模块详解与实操要点
3.1 登录认证模块:从明文密码到MD5加盐的渐进式教学
登录功能看似简单,却是安全意识启蒙的第一课。系统实现了从“明文传输”到“服务端加盐MD5”的完整演进路径,代码就在LoginServlet.java里:
// 步骤1:获取表单参数(教学重点:永远校验空值!)
String username = request.getParameter("username");
String password = request.getParameter("password");
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
request.setAttribute("error", "用户名或密码不能为空!");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
}
// 步骤2:查询数据库(教学重点:PreparedStatement防SQL注入!)
String sql = "SELECT id, username, password FROM user WHERE username = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username); // 参数化,杜绝 'admin' OR '1'='1'
ResultSet rs = pstmt.executeQuery();
// 步骤3:密码校验(教学重点:MD5加盐存储,非明文!)
if (rs.next()) {
String storedPassword = rs.getString("password"); // 数据库存的是MD5(密码+盐)
String salt = "zaixiancuoti_2024"; // 固定盐值,教学简化
String inputHash = DigestUtils.md5Hex(password + salt); // Apache Commons Codec
if (inputHash.equals(storedPassword)) {
// 登录成功,存入session
HttpSession session = request.getSession();
session.setAttribute("user", new User(rs.getInt("id"), username));
response.sendRedirect("index.jsp");
return;
}
}
request.setAttribute("error", "用户名或密码错误!");
request.getRequestDispatcher("/login.jsp").forward(request, response);
实操要点与避坑经验:
提示:
DigestUtils.md5Hex()需要引入commons-codec依赖,在pom.xml中已声明。学生常犯的错是直接用password.equals(storedPassword),导致永远登录失败——因为数据库存的是哈希值,不是明文!
注意:盐值salt写死在代码里是教学妥协(生产环境应动态生成并存库),但必须向学生强调:没有盐的MD5和明文密码一样危险。可以现场演示:用在线MD5工具输入”123456”,得到e10adc3949ba59abbe56e057f20f883e;再输入”123456zaixiancuoti_2024”,得到完全不同结果。这就是盐的价值。
实测心得:demo.sql中预置的管理员账号是admin/123456,其MD5加盐值已写入SQL,所以导入后直接可用。若学生想改密码,必须用相同盐值重新计算哈希,否则登录必败。readme.txt里专门写了密码重置步骤:“修改user表中password字段为MD5('新密码'+'zaixiancuoti_2024')”。
3.2 错题CRUD模块:JSP页面与Servlet逻辑的精准咬合
错题的增删改查是系统核心,其实现体现了“前后端职责分明”的教学理念。以“新增错题”为例,流程如下:
前端(add.jsp):
<form action="AddQuestionServlet" method="post">
<input type="text" name="title" placeholder="题目标题" required>
<textarea name="content" placeholder="题目内容(支持代码)" required></textarea>
<textarea name="answer" placeholder="参考答案" required></textarea>
<select name="category">
<option value="Java基础">Java基础</option>
<option value="数据库">数据库</option>
<option value="算法">算法</option>
</select>
<input type="number" name="difficulty" min="1" max="5" value="3" required>
<button type="submit">提交</button>
</form>
后端(AddQuestionServlet.java):
// 1. 获取参数(教学重点:对富文本内容做XSS过滤!)
String title = XssUtil.clean(request.getParameter("title")); // 过滤script标签
String content = XssUtil.clean(request.getParameter("content"));
String answer = XssUtil.clean(request.getParameter("answer"));
String category = request.getParameter("category");
int difficulty = Integer.parseInt(request.getParameter("difficulty"));
// 2. 封装Bean(教学重点:POJO是数据流转的枢纽)
Question question = new Question();
question.setTitle(title);
question.setContent(content);
question.setAnswer(answer);
question.setCategory(category);
question.setDifficulty(difficulty);
// 3. 调用DAO(教学重点:异常必须捕获并友好提示!)
try {
QuestionDao dao = new QuestionDao();
int result = dao.addQuestion(question);
if (result > 0) {
// 记录操作日志
LogDao logDao = new LogDao();
logDao.addLog((User) session.getAttribute("user"), "新增错题", question.getId(), request.getRemoteAddr());
request.setAttribute("msg", "添加成功!");
} else {
request.setAttribute("msg", "添加失败,请重试!");
}
} catch (SQLException e) {
e.printStackTrace(); // 开发期打印堆栈
request.setAttribute("msg", "系统繁忙,请稍后再试!");
}
request.getRequestDispatcher("list.jsp").forward(request, response);
关键教学点解析:
- XSS过滤:
XssUtil.clean()方法使用正则移除<script>、onerror=等危险标签,这是Web安全第一道防线。学生常忽略这点,直接将用户输入存入数据库,导致页面被注入恶意脚本。 - 异常处理:
catch (SQLException e)不是简单e.printStackTrace(),而是转向用户友好的提示页面。教学中强调:“用户不需要知道是MySQL还是Oracle出错,他只需要知道‘现在不能用’。” - 操作日志联动:新增错题后,立刻调用
LogDao.addLog()记录,体现“业务逻辑与审计日志”的解耦设计。学生可在此基础上扩展:比如删除操作时,日志里记录被删错题的标题(需JOIN查询),增强可追溯性。
3.3 搜索与分页模块:用最朴素的方式讲透数据处理逻辑
搜索和分页是学生最容易“抄错”的功能。系统采用纯服务端分页(非AJAX),代码在ListQuestionServlet.java中:
// 1. 获取分页参数(教学重点:默认值与边界校验!)
int currentPage = 1;
int pageSize = 10;
String pageParam = request.getParameter("page");
if (StringUtils.isNumeric(pageParam)) {
currentPage = Integer.parseInt(pageParam);
if (currentPage < 1) currentPage = 1; // 防止负数页
}
// 2. 获取搜索关键词
String keyword = request.getParameter("keyword");
if (keyword != null) keyword = keyword.trim();
// 3. 查询总记录数(教学重点:COUNT(*)是分页基石!)
int totalCount = questionDao.getCount(keyword);
int totalPage = (int) Math.ceil((double) totalCount / pageSize);
// 4. 查询当前页数据(教学重点:LIMIT offset, size!)
int offset = (currentPage - 1) * pageSize;
List<Question> questionList = questionDao.listQuestions(keyword, offset, pageSize);
// 5. 将数据与分页信息存入request
request.setAttribute("questionList", questionList);
request.setAttribute("currentPage", currentPage);
request.setAttribute("totalPage", totalPage);
request.setAttribute("totalCount", totalCount);
request.setAttribute("keyword", keyword);
request.getRequestDispatcher("list.jsp").forward(request, response);
配套JSP分页代码(list.jsp片段):
<!-- 显示总条数和当前页 -->
<div class="pagination-info">
共找到 ${totalCount} 条错题,当前第 ${currentPage} 页,共 ${totalPage} 页
</div>
<!-- 分页链接(教学重点:URL参数拼接!) -->
<div class="pagination">
<c:if test="${currentPage > 1}">
<a href="ListQuestionServlet?page=${currentPage-1}&keyword=${keyword}">上一页</a>
</c:if>
<c:forEach begin="1" end="${totalPage}" var="i">
<c:choose>
<c:when test="${i == currentPage}">
<span class="current">${i}</span>
</c:when>
<c:otherwise>
<a href="ListQuestionServlet?page=${i}&keyword=${keyword}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<c:if test="${currentPage < totalPage}">
<a href="ListQuestionServlet?page=${currentPage+1}&keyword=${keyword}">下一页</a>
</c:if>
</div>
实操心得:
提示:学生常把
offset算错,写成currentPage * pageSize,导致第1页显示第2页数据。必须强调:第1页的offset是0,第2页是10,第3页是20……公式是(页码-1)*每页条数。
注意:搜索关键词keyword通过URL参数传递,list.jsp中用${keyword}回显到搜索框,保证用户翻页时不丢失搜索条件。这是用户体验细节,也是教学重点——“状态保持”意识。
实测技巧:在demo.xlsx里准备了25条错题数据,导入demo.sql后,totalCount为25,pageSize=10,totalPage自然为3。学生可手动修改pageSize为5,观察分页效果变化,直观理解参数作用。
4. 部署与调试全流程:从IDEA导入到Tomcat运行的每一步
4.1 IDEA环境配置:避开“红叉地狱”的七步法
很多学生导入项目后,IDEA满屏红色,不是缺jar就是路径错。以下是经过百人验证的“零失败导入指南”:
- 解压与打开:将压缩包解压到无中文、无空格路径(如
D:\projects\zaixiancuoti),用IDEA选择Open,定位到zaixiancuoti文件夹(不是外层压缩包目录)。 - 识别Maven项目:IDEA会自动检测
pom.xml,弹出“Import Maven Project”提示,勾选Import project from external model→Maven,点击OK。 - 设置JDK:
File → Project Structure → Project,将Project SDK设为已安装的JDK 1.8(必须!JDK 11+会报javax.servlet找不到)。 - 设置Language Level:同上窗口,
Project language level选8 - Lambdas, type annotations etc.。 - 配置Maven:
File → Settings → Build → Build Tools → Maven,Maven home directory指向本地Maven安装路径(如D:\apache-maven-3.8.6),User settings file用默认即可。 - 刷新依赖:右键项目根目录 →
Maven → Reload,等待右下角“Building ‘zaixiancuoti’…”完成,External Libraries下应出现servlet-api-4.0.1.jar等依赖。 - 配置Artifacts(关键!):
File → Project Structure → Artifacts,点击+→Web Application: Archive→For 'zaixiancuoti:war exploded',Name填zaixiancuoti.war,Output Directory选D:\projects\zaixiancuoti\target\zaixiancuoti.war。点击OK后,勾选Build on make。
为什么这七步不可省略?
- 步骤3、4确保编译器用JDK 1.8语法,避免@Override报错;
- 步骤6的Reload强制IDEA下载mysql-connector-java等jar,否则运行时报ClassNotFoundException;
- 步骤7的Artifacts配置,决定了Tomcat部署时war包的生成路径和内容,漏掉这步,Tomcat里看不到项目。
4.2 MySQL数据库导入:字符集与SQL模式的双重保险
demo.sql和zaixiancuoti.sql虽标称“适配MySQL 5.7+”,但实际导入常因环境差异失败。以下是万能导入法:
- 启动MySQL服务:确保MySQL已运行(Windows服务里找
MySQL80,macOS用brew services start mysql)。 - 创建数据库(关键!):
sql CREATE DATABASE zaixiancuoti CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;提示:必须用
utf8mb4,而非utf8!MySQL的utf8实际是utf8mb3,不支持emoji和部分生僻汉字,会导致中文乱码。COLLATE utf8mb4_unicode_ci确保中文排序正确。 - 设置SQL模式(防报错):
sql SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));注意:MySQL 5.7默认开启
ONLY_FULL_GROUP_BY,而demo.sql中的某些统计查询未严格遵循该规则,此命令临时关闭,避免Expression #1 of SELECT list is not in GROUP BY clause错误。 - 执行SQL脚本:
- 方式一(推荐):用MySQL Workbench,右键zaixiancuoti数据库 →Table Data Import Wizard→ 选择demo.sql文件 → 下一步完成。
- 方式二(命令行):
bash mysql -u root -p zaixiancuoti < D:\projects\zaixiancuoti\demo.sql - 验证数据:
sql USE zaixiancuoti; SELECT COUNT(*) FROM question; -- 应返回5(demo.sql预置数据) SELECT * FROM user WHERE username='admin'; -- 密码字段应为MD5哈希值
常见问题速查表:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
ERROR 1067 (42000): Invalid default value for 'create_time' | MySQL 5.7严格模式禁止CURRENT_TIMESTAMP作为DATETIME默认值 | 执行SET GLOBAL sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';后重试 |
中文显示为??? | 数据库/表字符集非utf8mb4 | 执行ALTER DATABASE zaixiancuoti CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;,再ALTER TABLE question CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; |
Access denied for user 'root'@'localhost' | MySQL密码错误或权限不足 | 用mysql -u root -p登录后,执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES; |
4.3 Tomcat部署与启动:从war包生成到浏览器访问的闭环
部署是最后一公里,也是学生最易卡壳的环节:
- 配置Tomcat服务器:
Run → Edit Configurations → + → Tomcat Server → Local,Application server指向Tomcat 8.5安装目录(如D:\apache-tomcat-8.5.90)。 - 添加Deployment:点击
Deployment选项卡 →+→Artifact→ 选择zaixiancuoti:war exploded(注意是exploded,非war)→Application context填/zaixiancuoti(即访问路径为http://localhost:8080/zaixiancuoti)。 - 设置JVM参数(防内存溢出):
Configuration选项卡 →VM options填-Xms512m -Xmx1024m -Dfile.encoding=UTF-8。 - 启动服务器:点击绿色三角形,等待控制台输出
INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds。 - 访问验证:
- 浏览器打开http://localhost:8080/zaixiancuoti/login.jsp
- 输入admin/123456,应跳转至index.jsp
- 点击“错题列表”,应显示5条示例数据
关键细节说明:
提示:
Application context必须设为/zaixiancuoti,因为web.xml中所有<url-pattern>都基于此上下文。若设为空(/),则/login.jsp会变成根路径,与Servlet映射冲突。
注意:VM options中的-Dfile.encoding=UTF-8至关重要,它确保Tomcat读取JSP文件时用UTF-8编码,否则中文注释和页面文字会乱码。
实测心得:首次启动可能较慢(约30秒),因Tomcat需编译JSP为Servlet。后续修改JSP后,IDEA会自动热部署,无需重启Tomcat——这是开发效率的关键。
5. 常见问题与排查技巧实录:那些年我们踩过的坑
5.1 启动报错:java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet
现象:Tomcat启动时控制台抛出ClassNotFoundException,项目无法加载。
根本原因:IDEA未将servlet-api.jar加入Tomcat的classpath。虽然pom.xml声明了依赖,但Tomcat运行时需要它在lib目录下。
排查步骤:
1. 检查pom.xml中servlet-api依赖是否为provided范围:
xml <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> <!-- 必须是provided! --> </dependency>
2. 若为compile,改为provided并Reload Maven。
3. 在IDEA的Project Structure → Artifacts中,确认zaixiancuoti:war exploded的Output Layout里,WEB-INF/lib下没有servlet-api-4.0.1.jar(provided依赖不应打包)。
4. 最后,检查Tomcat安装目录lib下是否有servlet-api.jar(Tomcat 8.5自带,无需额外放置)。
解决方案:provided范围是唯一正解。学生常误将servlet-api设为compile,导致jar被复制到WEB-INF/lib,引发类加载冲突。
5.2 功能异常:登录成功后跳转到空白页或404
现象:输入正确账号密码,控制台显示Login success,但浏览器停留在login.jsp或显示HTTP Status 404。
根本原因:response.sendRedirect()的URL路径错误,或web.xml中Servlet映射失效。
排查步骤:
1. 查看LoginServlet.java中重定向语句:
java response.sendRedirect("index.jsp"); // 错!相对路径,应在当前上下文内 // 正确写法(推荐): response.sendRedirect(request.getContextPath() + "/index.jsp");
2. 检查web.xml中<servlet-mapping>是否匹配:
xml <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> <!-- 表单action必须是/login --> </servlet-mapping>
3. 确认login.jsp中的表单action:
```html
&spm=1001.2101.3001.5002&articleId=162138156&d=1&t=3&u=afde7620d8c644b58360e567387bc120)

被折叠的 条评论
为什么被折叠?



