ARTICLE DETAIL

资讯详情

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

JSP电子书下载系统从零实现:Servlet+JSP+MySQL建表到部署答辩

JSP电子书下载系统从零实现:Servlet+JSP+MySQL建表到部署答辩 简介这是一套基于Java/JSP的电子书下载系统毕业设计项目面向计算机相关专业需要完成Web方向课程设计或毕业设计的在校学生也适合初学Java Web开发的读者作为完整工程参考。系统前后台功能完整前台提供电子书分类查询、书名与作者检索、用户注册登录、点券充值、留言板、公告浏览与书籍下载后台则涵盖个人密码修改、会员信息查询与删除、点券充值审核、图书类别增删改查、电子书信息增删改查以及留言公告管理等模块。资源为RAR压缩包约89.36MB内含可直接运行的源码、MySQL 5数据库脚本、演示视频与配套论文源码经作者亲测可完整运行项目访问路径为http://localhost:8080/book。目前已有187人学习下载适合毕业设计答辩演示、课程项目复现以及希望掌握JSPServlet前后台完整开发流程的读者。1. JSP电子书下载系统毕业设计选题率最高但九成实现跑不到答辩JSP电子书下载系统在 Java Web 学习路径里出现的频率大概只比学生管理系统低半个身位。它一套系统覆盖数据库增删改查、会话保持、文件上传下载三条主线业务边界又很清晰正是两到三周能独立完成、做完还能讲明白的项目。这篇笔记用 Servlet JSP MySQL 8.0 的经典架构把从数据表设计到部署答辩的完整路径捋一遍。第一次写 Java Web 的可以照着做在 Spring Boot 里泡久了想回补底层的也值得扫。市面上带源码、数据库、视频和论文的电子书下载系统版本五花八门但核心的表结构和 Servlet 逻辑最后都能对齐到这套路线上。2. 架构选型与数据模型先想清楚表结构再写第一行 Java 代码电子书下载系统的业务闭环用一段话就能说清用户注册登录浏览分类、检索书名点击下载电子书文件管理员登录后台上传新书、维护书目、查看下载统计。在这个闭环里关键决策点只有两个——用不用框架表怎么建模。这两件事互相咬合用 Spring Boot MyBatis Plus字段能用注解自动建表用原生 Servlet JDBC表结构就得提前用 SQL 敲死。我推荐后者理由不是守旧而是这套系统的体量下JDBC 能精准定位问题。很多同学引入 mybatis-plus 做这个项目写 DAO 时觉得简单一遇到 tb_download 联合索引、自增 ID 和冗余计数字段就要去翻“根据实体类生成建表 SQL”的工具。本质原因不是工具不行而是对表结构失去掌控。先把表设计想明白后面每一行代码都是在给它填数据。2.1 Servlet JSP 分层不引框架反而是最好的学习路径这套项目的请求链路短得可怜JSP 提交表单 → Servlet 接收请求 → DAO 执行 SQL → 结果回传 JSP 渲染。这四条链路是 Java Web 的地基。刻意不用框架才能把地基打牢。常见做法是 Servlet 只做转发控制JSP 只负责渲染DAO 只做数据库增删改查。我在 bookList.jsp 里只用 JSTL EL 展示列表c:forEach items${bookList} varbook tr td${book.bname}/td td${book.author}/td td${book.downcount}/td td a href${pageContext.request.contextPath}/DownloadServlet?bid${book.bid}下载/a /td /tr /c:forEach这个 JSP 片段对应的 Servlet 链路是从 session 校验登录态调 BookDao.listBooks()把结果 setAttribute 后 forward 到 bookList.jsp一套典型的 MVC。这里最值得记住的是${pageContext.request.contextPath}这个 EL 表达式——它动态拼出项目部署路径避免写死。很多初版实现直接写a href/DownloadServlet本地部署在根路径没事一旦换到http://ip:8080/bookweb/这种上下文路径浏览器请求的/DownloadServlet根本不存在点击必然 404。凡是从源码包里抄来的页面第一件事就是全局搜索替换成这种相对路径写法。在 Java 端我守一条纪律Servlet 不写 SQLDAO 不碰 HttpSession。这套分层在答辩时帮过大忙——被问“以后要换数据库怎么办”答案就是改 DBUtil 连接串页面和 Servlet 一行不动。事务控制在 DAO 层用 try-catch 包住 Connection 做 commit/rollback而不是在 JSP 里写脚本片段。如果 JSP 页面里出现了大段% %Java 代码说明实现方式还停留在 jsp 当 Servlet 用的阶段该拆的拆、该合的合。2.2 七张表而不是五张下载记录表才是系统的灵魂很多初版实现只有三张表用户、图书、分类。勉强能跑但一答辩就露怯——“热门书目排行怎么来的”“用户行为数据在哪”一个号称电子书下载的系统没有下载行为记录等于业务数据没有沉淀。我常用的表结构是七张表名作用核心字段tb_user前台注册用户uid, uname, upwd, email, regtimetb_admin后台管理员aid, aname, apwd, last_logintb_category电子书分类cid, cname, cdesctb_book电子书元信息bid, bname, author, cid, filesize, filepath, cover, downcount, uploadtimetb_download下载行为记录id, uid, bid, down_time, iptb_favorite用户收藏id, uid, bid, add_timetb_comment用户评论id, bid, uid, content, comment_timetb_download 是我反复强调的一张表。它把一次点击变成一条可查记录于是“热门书目 TOP10”“每日下载量”都变成一行 GROUP BY SQL而不是拍脑袋填一个数。tb_favorite 和 tb_comment 看起来是加分项但它们直接决定了论文里的功能模块图能不能画满三层——没有这两张表你的系统就只有“用户 书目 下载”三个平面显得单薄。建表语句以 tb_book 为例CREATE TABLE tb_book ( bid INT PRIMARY KEY AUTO_INCREMENT, bname VARCHAR(200) NOT NULL COMMENT 书名, author VARCHAR(100) DEFAULT COMMENT 作者, cid INT NOT NULL COMMENT 分类ID, filesize BIGINT DEFAULT 0 COMMENT 文件大小单位字节, filepath VARCHAR(300) DEFAULT COMMENT 存储相对路径, cover VARCHAR(300) DEFAULT COMMENT 封面图路径, downcount INT DEFAULT 0 COMMENT 下载次数, uploadtime DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间, KEY idx_cid (cid), KEY idx_bname (bname) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个建表语句里有三个参数要说明。第一filesize 用 BIGINT 不用 INT因为电子书合集单文件超过 2GB 并不罕见INT 上限约 21 亿字节、也就是 2GB超出会报错或截断。第二downcount 是冗余计数为了列表页快速展示真实统计以 tb_download 为准。第三charset 用 utf8mb4 而不是 utf8——MySQL 的 utf8 实际是 utf8mb3不服从于完整 Unicode书名里出现生僻字会写入报错或存成问号。这属于典型的“看着能用、一踩就翻车”的细节。2.3 物理外键不建索引不能省上面建表语句里我没有写 FOREIGN KEY 子句。原因不是反对数据一致性而是物理外键在实际维护里容易被“绑架”删除分类前必须保证分类下没有书否则 SQL 直接报外键约束异常。这类异常在演示现场出现一次观感很差。常见做法是保留逻辑外键关系——表结构里 cid 的语义天然关联 tb_category但约束在 DAO 层用代码保证。比如删除分类前先查书目数量public boolean deleteCategory(int cid) { String check SELECT COUNT(*) FROM tb_book WHERE cid?; String delete DELETE FROM tb_category WHERE cid?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(check)) { ps.setInt(1, cid); ResultSet rs ps.executeQuery(); if (rs.next() rs.getInt(1) 0) { return false; // 分类下还有书拒绝删除 } try (PreparedStatement ps2 conn.prepareStatement(delete)) { ps2.setInt(1, cid); return ps2.executeUpdate() 0; } } catch (SQLException e) { e.printStackTrace(); return false; } }索引设计上至少建两列组合索引ALTER TABLE tb_download ADD KEY idx_bid_time (bid, down_time);组合索引的列顺序有讲究把等值过滤的 bid 放在前范围过滤的 down_time 放在后。反过来写索引在 WHERE bid? AND down_time BETWEEN ? AND ? 的场景下只能走单列效率差一个量级。这类细节在论文的数据库设计章节里写一句评审印象分会明显不一样。3. 可运行的工程落地登录、上传、下载的 Servlet 实现与部署步骤到了这一章假设你已经建好数据库、导入了七张表。接下来要做的是把项目跑起来重点看三个核心链路的编码登录会话控制、文件上传、文件下载。这三段代码覆盖了 JSP 项目里最容易出问题的三类坑也是答辩时评委最爱追问的地方。3.1 环境准备JDK 8 Tomcat 9 MySQL 8.0 最小配置环境版本先钉死。JDK 1.8 或 JDK 11 都行项目不用新版本语法Tomcat 9.0 对应 Servlet 4.0 规范MySQL 8.0。注意 MySQL 8.0 的 JDBC 驱动类是com.mysql.cj.jdbc.Driver不是老版本的com.mysql.jdbc.Driver——很多源码包是从 MySQL 5.x 时代流传下来的驱动不换连库必挂。数据库连接工具类放到 com.example.util 包下public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/bookdb ?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }三个连接参数逐个说明useUnicodetruecharacterEncodingutf8保证从 JDBC 写入数据库的字符串按 UTF-8 编码和表结构的 utf8mb4 配套serverTimezoneAsia/Shanghai是因为 MySQL 8.0 默认取服务器时区国内机器不指定会报 CST 约等于服务器时区错误useSSLfalse关掉本地开发的 SSL 告警。数据库脚本导入用一条命令即可mysql -u root -p bookdb.sql导入后立刻验证表数目SHOW TABLES;看到七张表再进下一步。很多部署翻车都和导入不完整有关后面表对不上代码一跑就爆空指针。3.2 登录注册与 Filter 权限拦截防绕过写法用户登录的核心逻辑就是一个 SQL 查询加一个会话写入。我常用的 LoginServlet 写法protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String uname request.getParameter(uname); String upwd request.getParameter(upwd); UserDao dao new UserDao(); User user dao.findByUname(uname); if (user ! null user.getUpwd().equals(MD5Util.md5(upwd))) { HttpSession session request.getSession(); session.setAttribute(user, user); response.sendRedirect(request.getContextPath() /bookList); } else { request.setAttribute(loginError, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }两个细节值得说明。第一密码不能明文存表MD5 在当前安全性已经不够但至少不是明文更正常的做法是加盐哈希比如 SHA-256 或 BCrypt这套代码后续可以平滑替换。第二登录成功用sendRedirect而不是forward因为重定向会让浏览器地址栏变成目标 URL刷新页面不会重复提交表单如果用 forward刷新一次就重复登录一次。权限控制的关键不在 LoginServlet 内部而在 Filter 层public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String uri request.getRequestURI(); // 白名单登录页、登录接口、注册接口、静态资源 if (uri.endsWith(/login.jsp) || uri.endsWith(/LoginServlet) || uri.endsWith(/RegisterServlet) || uri.contains(/static/)) { chain.doFilter(request, response); return; } if (session ! null session.getAttribute(user) ! null) { chain.doFilter(request, response); } else { response.sendRedirect(request.getContextPath() /login.jsp); } }白名单写法比黑名单靠谱。黑名单是“拦截已知危险”新加一个页面忘了列入黑名单就漏了白名单默认拦截一切只有明确放行的页面能绕过。想细分管理员权限可以在 Filter 里用request.getRequestURI().startsWith(/admin/)再判断一次 session 里的角色字段。3.3 电子书上传multipart 表单与文件路径分离书籍上传是管理员后台的核心功能。JSP 表单有两个关键点enctypemultipart/form-data必须写否则request.getParameter拿到的是 null文件选择控件要限制可接收类型。form action${pageContext.request.contextPath}/UploadServlet methodpost enctypemultipart/form-data input typetext namebname required select namecid option value1计算机/option option value2文学/option /select input typefile namefile accept.pdf,.epub,.mobi button typesubmit上传/button /formServlet 端接收 partMultipartConfig( maxFileSize 1024 * 1024 * 200, // 单文件最大 200MB maxRequestSize 1024 * 1024 * 250, // 整个请求最大 250MB fileSizeThreshold 1024 * 1024 // 超过 1MB 写入临时文件 ) public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); Part part request.getPart(file); String originalName part.getSubmittedFileName(); if (!originalName.endsWith(.pdf) !originalName.endsWith(.epub)) { request.setAttribute(uploadError, 只支持 PDF/EPUB 格式); request.getRequestDispatcher(/admin/upload.jsp).forward(request, response); return; } String uuidName UUID.randomUUID().toString() _ originalName; part.write(/data/ebooks/ uuidName); // 插入 tb_book 表bname, cid, filepath/data/ebooks/ uuidName, filesizepart.getSize() } }MultipartConfig的三个参数是上传功能的护身符maxFileSize 控制单文件上限maxRequestSize 控制整个 multipart 请求体上限fileSizeThreshold 表示超过 1MB 的文件先落临时文件再转存——防止大文件把内存打爆。part.write 的目标路径必须是配置文件里外置出来的不能写进项目 webapp 目录。这里存的是完整绝对路径也可以只存相对路径、下载时再拼配置前缀后面的避坑章节会展开讲。3.4 电子书下载Content-Disposition 与中文文件名下载是整系统的核心动作也是坑位最多的一段逻辑。我常用的 DownloadServlet 实现protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int bid Integer.parseInt(request.getParameter(bid)); BookDao dao new BookDao(); Book book dao.findById(bid); if (book null) { response.sendError(404, book not found); return; } dao.increaseDownCount(bid); File f new File(book.getFilepath()); if (!f.exists()) { response.sendError(404, file missing); return; } response.setContentType(application/octet-stream); String encodedName URLEncoder.encode(book.getBname(), UTF-8).replace(, %20); response.setHeader(Content-Disposition, attachment; filename*UTF-8 encodedName); try (InputStream in new BufferedInputStream(new FileInputStream(f)); OutputStream out response.getOutputStream()) { byte[] buf new byte[4096]; int len; while ((len in.read(buf)) ! -1) { out.write(buf, 0, len); } } }三个参数说明。第一filename*UTF-8是 RFC 5987 标准写法比老的filename更可靠支持中文和文件名里的空格URLEncoder.encode会把空格编码成所以要.replace(, %20)兜底。第二Content-Disposition必须放在setContentType之后顺序反了某些浏览器会忽略。第三try-with-resources保证输入输出流一定被关闭否则 Tomcat 会一直占用文件句柄下载几次之后文件就被锁住不用 try-with-resources 的话finally 里也要显式 close。4. 高频踩坑与排查记录从 404 到中文乱码再到下载文件损坏这一章是血泪经验汇总。这类 JSP 项目能跑到答辩的少倒不是功能做不出来而是被几个经典小坑卡到心态崩。每条按“现象 → 原因 → 解决”写照着排查即可。4.1 点击下载 404路径前缀和部署目录两处最容易犯错现象本地开发一切正常war 包部署到服务器后点“下载”按钮报 404JSP 页面上的静态图片也经常打不开。原因页面里写了硬编码绝对路径比如/DownloadServlet。本地部署成根路径时没问题一旦部署到http://ip:8080/bookweb/上下文浏览器请求的还是/DownloadServlet应用前缀bookweb丢了自然 404。另一个常见原因是 JSP 文件放在 WEB-INF 外直接访问没问题但相对路径会随目录层级变化而失效。解决全局搜索a href/、action/这类硬编码统一替换成${pageContext.request.contextPath}/前缀。静态资源用c:url生成 URL 也行效果一样。替换完重启再逐个页面点一遍路径类的 404 基本清零。4.2 中文乱码GET 和 POST 是两个独立战场现象注册用户名含中文入库后变成三个问号搜索框输入中文查不到结果。原因POST 请求体默认按 ISO-8859-1 解码数据库按 UTF-8 存取两头编码对不上GET 参数走 URI 解析由 Tomcat 的 URIEncoding 控制老版本默认不是 UTF-8。解决在读取任何参数之前执行request.setCharacterEncoding(UTF-8)放在 Filter 里统一处理更好server.xml 的 Connector 加URIEncodingUTF-8JDBC 连接串带characterEncodingutf8JSP 页面顶部声明% page contentTypetext/html;charsetUTF-8 %。四条同时满足中文问题才能根治少一条都可能偶发。4.3 下载的 PDF 打不开JSP 白空格混进响应流现象下载下来的 PDF 比原文件大几千字节打开提示文件损坏。原因下载逻辑写在 JSP 页面里页面前面的换行和空格被 Tomcat 当成响应内容输出到流破坏了二进制文件。这类写法在很早的源码包里很常见原封不动搬到 Servlet 里就没事了。解决下载逻辑全部收进 Servlet 的 doGetJSP 页面不做下载响应Servlet 里写响应之前不要输出任何内容注释也不要写流用 try-with-resources 包住确保 flush 及时。4.4 上传 20MB 文件报 500maxPostSize 的默认限制现象小文件能传大文件报 500 或控制台报 maxPostSize exceeded。原因Tomcat 对 POST 请求体大小有默认上限multipart 表单整个请求体超过直接拒绝MultipartConfig若没配 maxFileSize 也有默认限制两边夹击20MB 的 PDF 大概率被卡。解决server.xml 中该 Connector 设置maxPostSize00 表示不限制。同时MultipartConfig显式声明 maxFileSize 和 maxRequestSize和上传功能的设计值对齐。改完重启 Tomcat 生效不重启等于白改。4.5 上传文件重启就丢别把文件写进 webapps 目录现象上传成功Tomcat 重启后所有历史书名还在但点下载 404重新部署过更严重整个上传目录消失。原因文件被写进了 webapps 下的项目目录。Tomcat 执行 clean、重新部署时会清空项目目录文件自然没了。另一种情况是数据库存了本机绝对路径换台机器或者换操作系统路径直接失效。解决文件写到项目外部固定目录比如/data/ebooks/Linux或D:/ebooks/Windows数据库 filepath 字段存相对规范化的路径或绝对路径均可但必须保证路径由统一配置项拼出来方便换环境时只改一处。4.6 下载次数不涨浏览器缓存把请求吃了现象连续点下载十次tb_book.downcount 只涨一两次。原因浏览器对同一 URL 的响应做了缓存后续点击直接从本地缓存读请求根本没到服务器。下载响应没有设置禁用缓存的响应头是默认行为。解决DownloadServlet 里加两行响应头Cache-Control: no-store, no-cache, must-revalidate和Pragma: no-cache。但这里要留意缓存本身对服务器是保护业务统计的真实数据在 tb_download 表中逐条记录不受缓存影响downcount 只是展示用的冗余计数。排查时对比两个数值就能确认是缓存而不是计数逻辑写错。5. 答辩现场与上线前检查四个让系统看起来像生产级的细节5.1 准备充足预置数据答辩前把七张表灌上足量数据书目至少三千条用户一百个tb_download 里塞满两周的下载记录。没有数据支撑评委随便输个关键词查不到结果场面会很冷。预置数据别用手工 insert写一个 Java 批处理或 SQL 存储过程循环生成尽量让书名、作者分布有真实感。5.2 用 curl 验证下载和断点续传上线前我用 curl 做三个验证响应头、断点、缓存头。curl -I http://localhost:8080/bookweb/DownloadServlet?bid1 curl -H Range: bytes0-1023 -o test.bin http://localhost:8080/bookweb/DownloadServlet?bid1 curl -I -H Cache-Control: no-cache http://localhost:8080/bookweb/DownloadServlet?bid1第一条看 Content-Disposition 和 Content-Length 是否正常第二条验证 Range 请求能不能返回 206 和正确的片段第三条确认响应里带上了 no-cache。这三条跑通下载模块的底层就稳了。5.3 导出最新的 SQL 脚本带论文的源码包里的数据库脚本经常是作者早期版本的快照和你后来改的表结构对不上。答辩前重新mysqldump一份当前库替换掉原来的 bookdb.sql。这是最容易疏忽的事——代码是对的但别人拿旧脚本跑不出你的效果。5.4 论文用法论文里的“系统功能测试”一章直接把 tb_download 表的统计结果截图放进去。热门书目排行、每日下载趋势这些图都是数据自然长出来的比手画表格更有说服力。平时有导出数据的习惯这个工作十分钟就能搞定。这些年做过的 Java Web 项目返工率最高的永远是这类 JSP 作业题——不是功能难而是环境、路径、编码、IO 流这些基本功在悄悄要命。希望这篇笔记能帮你把坑提前踩平到答辩那天稳稳当当把项目讲完希望帮到你。本文还有配套的精品资源点击获取
返回列表