尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

JSP+Servlet+MySQL小说阅读管理系统课设实战:从建库到答辩避坑

发布时间:2026/9/28 16:34:24

资讯中心
01
ARTICLE

JSP+Servlet+MySQL小说阅读管理系统课设实战:从建库到答辩避坑

JSP+Servlet+MySQL小说阅读管理系统课设实战:从建库到答辩避坑
简介这是一套基于 JavaWeb 的小说阅读管理系统完整源码与课程设计报告面向计算机相关专业学生适用于 JavaWeb 课程设计、期末大作业或毕业设计参考。系统覆盖用户注册登录、书籍分类展示历史、都市、仙侠、奇幻、书籍内容查看与评论等典型功能模块结构清晰便于二次开发与学习。压缩包共包含 106 个文件以 Java 源码15 个、编译后 class 文件64 个、界面布局 frm 文件、数据库表 ibd 文件以及课程设计报告 docx 为主另附项目配置文件包体仅 5.86MB解压即可导入运行。附带 95 分以上课设报告提供完整设计思路与功能分析代码开箱即用无需修改即可确保运行。目前已有 433 人学习下载适合需要快速完成 JavaWeb 项目实战训练的读者。1. javaweb 小说阅读管理系统好在哪一题覆盖课设大半考点javaweb 小说阅读管理系统是 JavaWeb 课设里出题频率最高的题目之一。它表面是个“读书网站”实际上把登录注册、权限拦截、CRUD、多表关联、会话管理全部揉在了一起。反直觉的一点是评阅老师最看重的往往不是页面花不花哨而是三层架构是否清晰、数据库设计是否规范、登录态和 SQL 有没有明显漏洞。很多同学把 Bootstrap 皮肤套得很漂亮答辩时被一句“浏览器直接输入 admin.jsp 能进后台吗”问住反而界面朴素但 DAO/Service/Servlet 分层明确、SQL 全部走 PreparedStatement 的作业通常能稳拿 90 分以上。这个题目适合刚学完 JSP/Servlet/MySQL、需要一个能完整跑通“用户-管理端-数据库”链路的人也适合拿来当期末课程设计与毕业设计的底座。接下来我按选型、建库、写代码、部署、避坑一路讲完最后落到报告和答辩上。2. 设计先行JSPServletMySQL 的选型理由与数据库四表模型很多人的第一反应是“用 Spring Boot 不就行了”但课设场景里传统 javaweb 技术栈才是稳妥答案。这一章先把选型讲清楚再给出评审老师第一眼会看的包结构最后落到数据库表设计。2.1 为什么不用 Spring Boot 反而拿分更稳如果课程名是“JavaWeb程序设计”老师默认的验收口径是 JSP/Servlet/Filter/Session 这一套。用 Spring Boot 不是不能做而是答辩时你得多解释三层架构、自动配置、内嵌容器这些额外内容一旦答不上来反而从“加分项”变成“追问重灾区”。对比项JSPServletSpring Boot部署方式war 包放进 Tomcat webapps打 jar 或 war内嵌 Tomcat代码量样板代码多但可控自动配置省事黑匣子也多答辩被追问的点请求生命周期、Session、Filter都是课设考点自动装配、起步依赖、AOP 原理与课程要求匹配度通常正对口需要额外说明排错难度报错堆栈直接定位到 Servlet/DAO自动配置的坑需要翻底层我一般会建议除非老师明确允许 Spring Boot否则就用 JSPServletMySQL。这个选型能让“请求怎么进 Tomcat、Filter 怎么拦、Session 怎么存登录态”全部变成你的答辩素材。这恰好也是课设评分的核心。2.2 三层架构与包结构评审老师第一眼看什么老师打开源码包第一眼大概率不是看页面而是看 src 下的包名和目录结构。一个清晰的包结构比一个炫酷首页更容易建立第一印象。常见做法是这样分com.fiction.web Servlet 控制层处理请求参数和页面跳转 com.fiction.service 业务层接口和实现处理登录校验、收藏判断等业务规则 com.fiction.dao JDBC 数据访问层只负责 SQL 和结果集封装 com.fiction.entity 实体类与数据表字段一一对应 com.fiction.filter 登录拦截、字符编码拦截 com.fiction.util 数据库连接工具类 webapp/ admin/ 管理端 JSP书籍管理、章节管理 book/ 用户端列表页、详情页、阅读页 user/ 登录注册、个人中心、我的书架 css/ js/ images/ 静态资源这套分层对应 MVCJSP 是 ViewServlet 是 ControllerServiceDAO 是 Model。写代码前先把这个骨架在 IDEA 里建好后面所有功能都往固定位置塞不会乱。有三个边界值得注意实体类字段名尽量和表列名一致能省掉一堆手工 setter 转换DAO 里不要出现 HttpServletRequest、HttpSession 这类 Web 对象Service 层方法里做事务控制比如“收藏的同时更新收藏数”必须在一个事务内。课设规模不大事务边界不用做得很重但至少要在 Service 层体现这个意识。2.3 用户、小说、章节、收藏四张表的模型设计小说阅读管理系统的核心数据模型是四张表用户表、小说表、章节表、收藏表。分类不单独建表直接在小说表里放一个 category 字段就够了避免课设规模下不必要的 JOIN。表名字段说明t_userid, username, password, nickname, role, create_timerole 区分普通用户和管理员t_bookid, title, author, category, intro, hits, statusstatus 控制上架/下架t_chapterid, book_id, title, content, order_nocontent 用 LONGTEXT 存正文t_favoriteid, user_id, book_id, create_time唯一索引 user_id book_id关系是这样的t_book 对 t_chapter 是一对多t_user 对 t_book 通过 t_favorite 实现多对多。章节表的 order_no 用于排序而不是直接依赖 id这样即使章节有删改也能保持阅读顺序稳定。t_favorite 表必须加唯一索引(user_id, book_id)这是后续做去重收藏的前提。如果这一步漏了后面代码里再怎么判断“是否已收藏”都会有并发漏洞。数据库表设计是课设报告里最占篇幅的部分四张表画清楚关系图报告和答辩就都有了底气。3. 核心代码实现登录拦截、小说列表、章节阅读与收藏这一章直接给可复制的代码路径。按“建库 - 连库 - 登录 - 列表 - 收藏”的顺序走每一步都对应源码包里最核心的那几个类。3.1 初始化数据库建库脚本与样例数据拿到源码包后第一件事永远是执行 SQL 脚本而不是先看报告。把下面脚本存成 fiction_db.sql在 Navicat 或命令行里执行一遍-- 小说阅读管理系统数据库初始化脚本 CREATE DATABASE IF NOT EXISTS fiction_db DEFAULT CHARACTER SET utf8mb4; USE fiction_db; DROP TABLE IF EXISTS t_favorite; DROP TABLE IF EXISTS t_chapter; DROP TABLE IF EXISTS t_book; DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL DEFAULT , nickname VARCHAR(50) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 1普通用户2管理员, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_book ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(100) NOT NULL, author VARCHAR(50) DEFAULT NULL, category VARCHAR(20) DEFAULT NULL, intro TEXT, hits INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架0下架, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_chapter ( id INT NOT NULL AUTO_INCREMENT, book_id INT NOT NULL, title VARCHAR(100) NOT NULL, content LONGTEXT, order_no INT NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_book (book_id), CONSTRAINT fk_chapter_book FOREIGN KEY (book_id) REFERENCES t_book (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_favorite ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_book (user_id, book_id), CONSTRAINT fk_fav_user FOREIGN KEY (user_id) REFERENCES t_user (id) ON DELETE CASCADE, CONSTRAINT fk_fav_book FOREIGN KEY (book_id) REFERENCES t_book (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 样例数据一个管理员三本书每本书一个章节 INSERT INTO t_user (username, password, nickname, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 管理员, 2); INSERT INTO t_book (title, author, category, intro, hits, status) VALUES (三体, 刘慈欣, 科幻, 地球人类文明和三体文明的信息交流、生死搏杀及两个文明在宇宙中的兴衰历程。, 120, 1), (活着, 余华, 文学, 讲述了农村人福贵悲惨的人生遭遇。, 98, 1), (小王子, 圣埃克苏佩里, 童话, 一个飞行员和小王子在沙漠中相遇的故事。, 86, 1); INSERT INTO t_chapter (book_id, title, content, order_no) VALUES (1, 第一章 科学边界, 汪淼觉得事情在变得复杂……, 1), (2, 第一章 赌, 我比现在年轻十岁的时候……, 1), (3, 第一章 我和大人们, 当我还只有六岁的时候……, 1);这段脚本里有几个参数值得说明数据库字符集用 utf8mb4而不是 utf8因为小说正文可能出现特殊符号和表情utf8mb4 能兼容更多字符。t_user 的 password 字段存的是123456的 MD5 值e10adc3949ba59abbe56e057f20f883e这样数据库里不会出现明文密码。外键都加了ON DELETE CASCADE删掉一本书时章节和收藏记录会自动跟着清掉答辩被问到“删除书之后数据会不会变成孤儿数据”时这是一个很稳的回答。如果老师的 MySQL 是 5.5 或更老版本COMMENT 语法可能提示版本不兼容把 COMMENT 部分删掉即可不影响表结构。3.2 统一数据库连接DBUtil 与 db.properties所有 DAO 都要拿 Connection把连接逻辑抽成一个工具类是必须的。下面是通用写法package com.fiction.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class DBUtil { private static String url; private static String username; private static String password; // 静态代码块在类加载时执行连接配置只读一次 static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); // 手动注册驱动兼容 MySQL 5.x 驱动包 Class.forName(props.getProperty(jdbc.driver)); } catch (Exception e) { // 初始化失败要尽早暴露不能拖到 getConnection 才报 throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(ResultSet rs, Statement st, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { } } if (st ! null) { try { st.close(); } catch (SQLException e) { } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { } } } }对应的 db.properties 放在 src 根目录下jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/fiction_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456这里的参数解释一下如果用的是 MySQL 8.x 的驱动包jdbc.driver要写成com.mysql.cj.jdbc.Driver连接串里的serverTimezoneAsia/Shanghai必须保留否则 MySQL 8 会报时区错误。characterEncodingutf8管的是数据库连接的编码和 JSP 页面编码是两回事漏掉这一个最容易出中文乱码。用DriverManager而不是数据库连接池是课设的常规选择。连接池比如 C3P0、Druid在真实项目里是标配但如果课设要求里没提引入它反而增加答辩负担。守住“能用、够用、讲得清”这条线。3.3 登录与访问控制LoginServlet AuthFilter 的配合登录模块是课设里被问得最多的模块核心就两件事登录成功后把用户放进 Session未登录请求全部被 Filter 拦下。先看 LoginServletpackage com.fiction.web; import com.fiction.entity.User; import com.fiction.service.UserService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 先做非空校验避免空指针打到日志里 if (username null || password null || username.trim().isEmpty() || password.trim().isEmpty()) { req.setAttribute(error, 用户名和密码不能为空); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } User user userService.login(username.trim(), password); if (user null) { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } else { // 登录成功把用户对象放进 session后续 Filter 就认这个标记 req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /book?actionlist); } } }再写一个拦截所有请求的 AuthFilterpackage com.fiction.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebFilter(/*) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); String ctx req.getContextPath() /; String path uri.substring(ctx.length()); // 白名单登录页、登录请求、注册页、静态资源全部放行 if (login.jsp.equals(path) || login.equals(path) || register.jsp.equals(path) || path.startsWith(css/) || path.startsWith(js/) || path.startsWith(images/)) { chain.doFilter(request, response); return; } Object loginUser req.getSession().getAttribute(loginUser); if (loginUser null) { // 未登录统一回登录页用重定向而不是转发避免 URL 停留到受保护页面 resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }这段代码的关键参数是拦截范围/*和后面的白名单判断。很多人写 Filter 只拦/book或/admin结果用户直接在地址栏输入某个 JSP 路径就能绕过登录这是评审老师最爱抓的问题。登录失败用 forward转发到 login.jsp 并携带 error 提示登录成功用 redirect避免刷新页面时重复提交表单。3.4 小说列表和详情一条请求从 JSP 到 DAO 再回到 JSP小说模块我用一个 BookServlet 处理列表和详情两种动作用 action 参数区分。课程设计里 Servlet 数量不用贪多一个 Servlet 对应一类资源是更清晰的做法。package com.fiction.web; import com.fiction.entity.Book; import com.fiction.entity.Chapter; import com.fiction.service.BookService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; WebServlet(/book/*) public class BookServlet extends HttpServlet { private BookService bookService new BookService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getPathInfo(); if (action null) { resp.sendRedirect(req.getContextPath() /book/list); return; } switch (action) { case /list: ListBook books bookService.listOnSaleBooks(); req.setAttribute(books, books); req.getRequestDispatcher(/book/list.jsp).forward(req, resp); break; case /detail: int bookId Integer.parseInt(req.getParameter(id)); Book book bookService.getBookDetail(bookId); ListChapter chapters bookService.listChapters(bookId); req.setAttribute(book, book); req.setAttribute(chapters, chapters); req.getRequestDispatcher(/book/detail.jsp).forward(req, resp); break; default: resp.sendError(404); } } }对应的 DAO 方法用 PreparedStatement 做查询public ListBook listOnSaleBooks() throws SQLException { String sql SELECT id, title, author, category, intro, hits FROM t_book WHERE status 1 ORDER BY hits DESC; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setCategory(rs.getString(category)); b.setIntro(rs.getString(intro)); b.setHits(rs.getInt(hits)); list.add(b); } } return list; }这里有两个参数层面的细节status 1过滤掉下架书籍课设里如果管理员下架了一本书用户端列表不应该还能看到ORDER BY hits DESC让热门小说排前面是功能上最直观的“亮点”。JDBC 用 try-with-resources 会自动关闭 Connection、Statement、ResultSet比手写 finally 更可靠也少几行样板代码。详情页的点击量累计也很简单进入详情时先执行UPDATE t_book SET hits hits 1 WHERE id ?再走一次查询。严格说应该把两次操作放进一个事务课设界面下不强调但在报告里写一句“阅读量更新与查询之间存在事务边界”会被认为是考虑过边界的人。3.5 收藏与书架多对多关系用 INSERT IGNORE 去重收藏表是用户和书籍之间的多对多关系实现上最容易犯的错是“先查再插”。正确做法是依赖唯一索引配合INSERT IGNORE一步到位public boolean addFavorite(int userId, int bookId) { // 依赖 t_favorite 上的唯一索引 (user_id, book_id) 去重 String sql INSERT IGNORE INTO t_favorite (user_id, book_id) VALUES (?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, userId); ps.setInt(2, bookId); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }查询我的书架时用 JOIN 把收藏表和书籍表关联起来public ListBook listFavoriteBooks(int userId) { String sql SELECT b.id, b.title, b.author, b.category, b.hits FROM t_favorite f JOIN t_book b ON f.book_id b.id WHERE f.user_id ? ORDER BY f.create_time DESC; // 结果集封装同 listOnSaleBooks省略重复代码 }INSERT IGNORE的返回值是关键受影响行数为 0 就说明重复了。这样无论是快速双击收藏按钮还是开两个页面同时点击都不会插入重复数据。如果不用这条 SQL而是先 SELECT 判断、再 INSERT在高一点的并发下很容易产生重复记录答辩时把唯一索引设计讲出来比“我做了个判断”更有说服力。JSP 里收藏按钮用 form 提交而不是超链接。原因是收藏操作改变了服务器数据按 HTTP 语义应该用 POSTform methodpost action${pageContext.request.contextPath}/favorite?actionadd input typehidden namebookId value${book.id} button typesubmit加入书架/button /form4. javaweb 项目在 IDEA 里运行起来Tomcat 配置和三个自测用例源码拿到手跑不起来等于零。这一章只讲 IDEA 里运行 javaweb 项目最关键的几步以及跑通后怎么自测。4.1 创建 Web 项目、添加 Tomcat 与热部署配置IDEA 中运行 javaweb 项目标准路径是新建 Java Enterprise 项目选择 Web Application 模板如果拿到的是普通 Java 项目先手动添加 Web Facet把 src 目录和 webapp 目录结构对好。然后按下面几步配置运行环境1. File - Settings - Build, Execution, Deployment - Application Servers 添加本地 Tomcat 目录IDEA 会自动识别 Tomcat 版本 2. Run - Edit Configurations - 左上角 - Tomcat Server - Local 3. Deployment 页签 - 添加 Artifact选择 xxx:war exploded 4. Server 页签 - Application context 填 /fiction 5. 端口默认 8080被占用就改成 8081 或 8082 6. 勾选 After launch浏览器选择 Chrome 或默认浏览器war exploded 和 war 的区别值得多说一句exploded 是展开后的目录IDEA 直接从这个目录加载项目改 JSP 和 Java 代码都支持热部署开发调试用它war 是一个压缩包答辩前打成 war 放进 Tomcat 的 webapps 目录给老师留存。这是“开发时用 exploded交付时用 war”的固定习惯。配置完先不要急着点运行用下面这条命令确认 Tomcat 端口能通# Windows 下直接在浏览器访问Linux/macOS 下用 curl 验证 curl -I http://localhost:8080/fiction/login.jsp # 期望输出 HTTP/1.1 200如果是 404 说明 Application context 或部署路径不对这一步能帮你区分“项目没部署成功”和“项目部署了但路径不对”这两种情况。Tomcat 启动后如果端口被占用日志里会有一行清晰的Address already in use换端口重试即可。4.2 连接 MySQL 必须改的 4 个参数数据库连不上、中文乱码十有八九是 db.properties 里的参数没改对。我一般拿到项目先打开这个文件重点核对下表里的四项参数常见错误值报错或现象正确做法jdbc.url 里的库名写成 fictionUnknown database改成脚本里建的 fiction_dbjdbc.username写死 testAccess denied改成自己 MySQL 的实际账号jdbc.password密码带了空格Access denied确保没有意外空格serverTimezone不写或写错The server time zone value写 Asia/ShanghaiMySQL 8.x 的驱动类名是com.mysql.cj.jdbc.DriverMySQL 5.x 是com.mysql.jdbc.Driver这两个不能混用。驱动 jar 要放进WEB-INF/lib目录而不是只加到 IDEA 的 Libraries 里否则部署到 Tomcat 后运行时会找不到驱动类。4.3 部署后的三个自测用例项目能启动只是第一步要确认功能链路真的通我建议按下面三个用例走一遍用例 1登录与未登录拦截 步骤访问 /fiction/login.jsp - 输入 admin / 123456 - 应跳转 /book?actionlist 退出浏览器再访问 /book/list - 应被重定向回 login.jsp 验证点Session 登录态和 AuthFilter 是否生效 用例 2小说列表详情与点击量 步骤进入列表页 - 打开《三体》详情 - 查看第一章正文 返回列表页再次进入详情 - hits 从 120 变成 121 验证点JDBC 查询、详情跳转、点击量更新是否正常 用例 3收藏去重 步骤登录后收藏《活着》两次 - 刷新我的书架 验证点t_favorite 表中只有一条记录页面不出现重复书籍这三个用例覆盖了登录、查询、写入、多表关联四条核心链路。任何一条失败问题大概率出在两个位置Filter 配置和 SQL 语句。先在源码里把这两处对一遍再去看日志堆栈。5. 课设避坑指南评审老师最常抓的 6 个扣分点这一章是血泪经验汇总。下面每个问题都是课设答辩现场真实出现过的翻车场景按“现象 - 原因 - 解决”来写。5.1 中文乱码请求乱码和响应乱码要分开处理现象小说正文从数据库读出来全是??或者登录后用户名变成乱码显示在页面上。原因数据库连接串少了characterEncodingutf8或者 JSP 页面没有声明 UTF-8又或者 POST 请求参数没有设置解码字符集。最容易被忽视的是 Tomcat 7 及更早版本对 GET 请求的 URI 默认按 ISO-8859-1 解码地址栏带中文参数时直接就乱了。解决三处一起改。db.properties 的连接串带上characterEncodingutf8每个 JSP 头部写% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %再加一个字符编码 Filter对所有请求统一设置编码WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); chain.doFilter(req, resp); } }注意EncodingFilter 和 AuthFilter 都拦/*过滤器的执行顺序在注解方式下不可控。如果要保证编码 Filter 先执行把它写进 web.xml 的 filter-mapping 并放在最前面这是最稳的做法。5.2 密码明文入库被指出的概率仅次于乱码现象老师打开数据库看到password字段存的是123456脸色立刻变了。原因为了演示方便直接存的明文没有做任何处理。解决至少做一层 MD5。虽然 MD5 不是安全的密码哈希方案但在课设层面足够展示“我没有把明文密码直接落库”的意识。登录时把用户输入的密码也做同样的 MD5 再比对。用上一步 SQL 脚本里的样例账号admin 的密码是123456的 MD5 值登录逻辑里摘要后比对即可public User login(String username, String password) { // 把明文摘要后和库里的摘要比对 String md5 DigestUtils.md5Hex(password); String sql SELECT id, username, nickname, role FROM t_user WHERE username ? AND password ?; // 执行查询命中则封装 User否则返回 null }5.3 登录态形同虚设绕过 Filter 直达 JSP现象未登录状态下浏览器直接输入/fiction/user/center.jsp页面竟然能打开甚至管理员的 book_add.jsp 也能访问。原因Filter 只拦截了/book/*和/admin/*这类路径但 JSP 本身就在 webapp 目录下Tomcat 允许直接访问。登录控制的漏洞比任何功能缺失都严重。解决AuthFilter 拦截范围写成/*再用白名单放行 login.jsp、login、register.jsp、css、js。管理员接口还要再判断role字段普通用户即使登录了访问管理页也应该被拦。把 Filter 的拦截链路图画到课设报告里这是非常直观的加分素材。5.4 首页 404 和静态资源丢失现象部署后访问http://localhost:8080/fiction/直接 404或者列表页能打开但 CSS 全乱了。原因web.xml 里没有配置welcome-file-listTomcat 找不到默认首页静态资源用相对路径css/style.css引用当 URL 变成/book/detail时浏览器解析到的路径就不对了。解决web.xml 里加欢迎页welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-listJSP 里所有静态资源都要带上下文路径前缀推荐在 jsp 头部用 EL 取link relstylesheet href${pageContext.request.contextPath}/css/style.css5.5 SQL 注入与手写字符串拼接现象登录框输入 or 11竟然能登录成功。原因DAO 里用了字符串拼接 SQL。老师只要看到一处SELECT ... WHERE username username 后面的答辩基本都会围绕这个问题展开。解决所有动态 SQL 全部走 PreparedStatement参数用?占位调用setString、setInt绑定。如果源码包里有任何拼接 SQL答辩前一定全部改掉。5.6 收藏重复数据和前端重复提交现象双击“加入书架”按钮收藏表里同一个用户和书出现了两条记录收藏成功后按 F5 刷新收藏又多了一条。原因没有唯一索引或者 DAO 用了“先查再插”的逻辑并发下判断失效前端用了超链接 GET 请求刷新会重复执行。解决数据库建表时加UNIQUE KEY uk_user_book (user_id, book_id)DAO 用INSERT IGNORE。前端收藏按钮用 POST 提交成功之后可以跳转到书架列表页这样刷新也不会重复写入。6. 冲刺 95 分课设报告写法与答辩演示路径6.1 报告写什么课设报告的核心是“图多字少”。数据库关系图、系统流程图、Filter 拦截链路图这三张图放到报告里比大段文字管用得多。功能描述不要堆页面截图每个模块截一张关键页面加一段说明就够了。数据库设计章节把四张表的字段含义、外键关系写清楚这通常是评阅老师第一个翻看的部分。6.2 答辩时的演示顺序演示时先走用户端完整链路注册登录 - 浏览列表 - 打开详情 - 阅读章节 - 加入书架再切到管理端新增一本书 - 给书添加章节 - 下架这本书最后把登录 Filter 的代码和 PreparedStatement 的使用各讲一分钟。亮点前置不要把时间浪费在讲首页布局上。6.3 两个加分功能时间充裕的话可以加两个轻量功能阅读进度记忆和热门榜。进度记忆可以建一张t_progress表记录用户最后阅读的章节 id再次进入小说详情时定位到对应章节热门榜直接在列表页ORDER BY hits DESC再加一个按分类筛选的下拉框。这两个功能代码量不大但能让答辩内容从“做了个管理系统”变成“考虑过用户留存和场景完整度”。我自己的习惯是拿到这类题目第一步永远不是写代码而是把四张表和 Filter 拦截的路径画在纸上画完整套系统的边界就清楚了后面所有操作都是往这个框架里填东西。这个习惯帮我避开了大多数翻车现场希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。