Java Web错题管理系统源码包(含MySQL建库脚本、IDEA工程与部署说明)

该文章已生成可运行项目,

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的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注解,必须理解HttpServletRequestHttpServletResponse的流转,必须在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.sqlzaixiancuoti.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表承载全部错题管理功能,categorydifficulty字段预留了未来按分类/难度筛选的接口;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=10totalPage自然为3。学生可手动修改pageSize为5,观察分页效果变化,直观理解参数作用。

4. 部署与调试全流程:从IDEA导入到Tomcat运行的每一步

4.1 IDEA环境配置:避开“红叉地狱”的七步法

很多学生导入项目后,IDEA满屏红色,不是缺jar就是路径错。以下是经过百人验证的“零失败导入指南”:

  1. 解压与打开:将压缩包解压到无中文、无空格路径(如D:\projects\zaixiancuoti),用IDEA选择Open,定位到zaixiancuoti文件夹(不是外层压缩包目录)。
  2. 识别Maven项目:IDEA会自动检测pom.xml,弹出“Import Maven Project”提示,勾选Import project from external modelMaven,点击OK。
  3. 设置JDKFile → Project Structure → Project,将Project SDK设为已安装的JDK 1.8(必须!JDK 11+会报javax.servlet找不到)。
  4. 设置Language Level:同上窗口,Project language level8 - Lambdas, type annotations etc.
  5. 配置MavenFile → Settings → Build → Build Tools → MavenMaven home directory指向本地Maven安装路径(如D:\apache-maven-3.8.6),User settings file用默认即可。
  6. 刷新依赖:右键项目根目录 → Maven → Reload,等待右下角“Building ‘zaixiancuoti’…”完成,External Libraries下应出现servlet-api-4.0.1.jar等依赖。
  7. 配置Artifacts(关键!):File → Project Structure → Artifacts,点击+Web Application: ArchiveFor '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.sqlzaixiancuoti.sql虽标称“适配MySQL 5.7+”,但实际导入常因环境差异失败。以下是万能导入法:

  1. 启动MySQL服务:确保MySQL已运行(Windows服务里找MySQL80,macOS用brew services start mysql)。
  2. 创建数据库(关键!):
    sql CREATE DATABASE zaixiancuoti CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

    提示:必须用utf8mb4,而非utf8!MySQL的utf8实际是utf8mb3,不支持emoji和部分生僻汉字,会导致中文乱码。COLLATE utf8mb4_unicode_ci确保中文排序正确。

  3. 设置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错误。

  4. 执行SQL脚本
    - 方式一(推荐):用MySQL Workbench,右键zaixiancuoti数据库 → Table Data Import Wizard → 选择demo.sql文件 → 下一步完成。
    - 方式二(命令行):
    bash mysql -u root -p zaixiancuoti < D:\projects\zaixiancuoti\demo.sql
  5. 验证数据
    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包生成到浏览器访问的闭环

部署是最后一公里,也是学生最易卡壳的环节:

  1. 配置Tomcat服务器Run → Edit Configurations → + → Tomcat Server → LocalApplication server指向Tomcat 8.5安装目录(如D:\apache-tomcat-8.5.90)。
  2. 添加Deployment:点击Deployment选项卡 → +Artifact → 选择zaixiancuoti:war exploded(注意是exploded,非war)→ Application context/zaixiancuoti(即访问路径为http://localhost:8080/zaixiancuoti)。
  3. 设置JVM参数(防内存溢出):Configuration选项卡 → VM options-Xms512m -Xmx1024m -Dfile.encoding=UTF-8
  4. 启动服务器:点击绿色三角形,等待控制台输出INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds
  5. 访问验证
    - 浏览器打开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.xmlservlet-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,改为providedReload Maven
3. 在IDEA的Project Structure → Artifacts中,确认zaixiancuoti:war explodedOutput 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

``` **解决方案**:统一使用`request.getContextPath()`构造绝对路径,避免相对路径歧义。这是Web开发铁律。 ### 5.3 数据异常:错题列表显示中文乱码或时间错误 **现象**:`list.jsp`中错题标题显示为`????`,或`create_time`显示为`1970-01-01 08:00:00`。 **根本原因**:MySQL连接URL未指定字符集和时区。 **排查步骤**: 1. 检查`src/main/resources/db.properties`: ```properties url=jdbc:mysql://localhost:3306/zaixiancuoti?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai ``` 2. 若缺少`characterEncoding=utf8mb4`,则中文存取乱码;若缺少`serverTimezone=Asia/Shanghai`,则MySQL时间戳与Java时间不同步。 **解决方案**:在`db.properties`中补全参数,并重启Tomcat。这是数据库连接配置的黄金三参数:`useUnicode=true`、`characterEncoding=utf8mb4`、`serverTimezone=Asia/Shanghai`。 ### 5.4 日志异常:`my.log`为空或不生成 **现象**:`log/my.log`文件存在但大小为0,或根本没生成。 **根本原因**:`LogUtil.java`中日志路径硬编码,或文件权限不足。 **排查步骤**: 1. 查看`LogUtil.java`中日志文件路径: ```java private static final String LOG_FILE_PATH = "D:/projects/zaixiancuoti/log/my.log"; ``` 2. 若学生解压路径不是`D:/projects/zaixiancuoti`,则路径无效。 3. 检查`log`目录是否存在且有写入权限。 **解决方案**:将路径改为相对路径:
private static final String LOG_FILE_PATH = System.getProperty("user.dir") + "/log/my.log";
`System.getProperty("user.dir")`获取项目根目录,确保跨环境通用。同时,在`readme.txt`中提醒学生首次运行前手动创建`log`文件夹。 ## 6. 毕业设计延伸建议:从“能跑”到“能讲”的跃迁路径 这套系统不是终点,而是起点。学生若想在答辩中脱颖而出,可基于此做三个层次的延伸,每个都紧扣“展示能力”而非“堆砌功能”: ### 6.1 层次一:功能深化(体现工程能力) - **错题导出Excel**:在`list.jsp`增加“导出”按钮,后端用Apache POI生成`demo.xlsx`同格式文件。这展示了“数据流转能力”——从数据库查出List,转换为Excel流,再通过`response.getOutputStream()`写回浏览器。代码量不大,但涉及IO、流操作、MIME类型设置(`response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")`),是很好的加分项。 - **模糊搜索优化**:将`LIKE '%${keyword}%'`改为全文索引(`ALTER TABLE question ADD FULLTEXT(title, content)`)配合`MATCH AGAINST`,并对比搜索耗时。这体现了“性能意识”,答辩时可展示执行计划(`EXPLAIN`),说明为何全文索引比`LIKE`快。 ### 6.2 层次二:架构演进(体现设计思维) - **抽取BaseServlet**:创建`BaseServlet extends HttpServlet`,在`doGet/doPost`中统一处理`request.setCharacterEncoding("UTF-8")`、`response.setContentType("text/html;charset=UTF-8")`、`session`校验(判断是否登录)。所有业务Servlet继承它。这展示了“抽象复用”思想,代码从重复变简洁,架构图上可画出清晰的继承关系。 - **DAO层接口化**:定义`QuestionDaoInterface`接口,`QuestionDao`实现它。后续可轻松替换为`QuestionDaoMyBatisImpl`,体现“面向接口编程”。答辩时可说:“如果项目后期要接入MyBatis,只需新增一个实现类,业务层代码零修改。” ### 6.3 层次三:教学价值挖掘(体现思考深度) - **在`readme.txt`中增加“设计决策说明”章节**:解释为何用JDBC而非MyBatis(教学透明性)、为何不分页用AJAX(降低前端复杂度)、为何日志表不记录详细内容(隐私保护)。这不再是“怎么做”,而是“为什么这么做”,展现工程师的权衡能力。 - **录制3分钟功能演示视频**:用OBS录制从启动Tomcat、登录、新增错题、搜索、导出的全流程,旁白解说关键技术点(如“这里用了PreparedStatement防注入”“这个时间戳是MySQL自动填充的”)。视频嵌入答辩PPT,比纯文字描述更有说服力。 最后分享一个小技巧:答辩前夜,务必用另一台干净电脑(或虚拟机)从头走一遍“下载→解压→导入→建库→部署→登录→操作”全流程。这不仅能发现隐藏的环境依赖,更能让你在答辩时底气十足地说出:“老师,这个系统我在三台不同配置的电脑上都验证过,从零开始20分钟内必能跑通。”——这份笃定,比任何华丽的功能都更能打动评委。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的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环境中启动运行,适合计算机类专业学生直接用于毕业设计或课程实训。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值