ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Java Servlet/JSP+MySQL的电子书下载系统毕业设计源码解析

基于Java Servlet/JSP+MySQL的电子书下载系统毕业设计源码解析

简介:基于Java的电子书下载系统毕业设计资料包,面向计算机相关专业毕业生或正在完成课程设计的学生,覆盖项目报告、源代码、答辩PPT与数据库脚本,可支撑从系统设计到答辩展示的全流程。资料以zip压缩包提供,共7个文件,包含JPG项目截图、SQL数据库脚本、DOC项目说明报告以及ZIP格式的源代码工程,包体约5.79MB,整体轻量、结构直观。已有114人学习下载,适合需要参考Java Web项目完整开发流程、数据库设计或答辩提纲的读者。价值方面,读者可获得一份可运行的项目源码,便于理解电子书下载系统的模块划分与实现逻辑;项目报告与答辩PPT能帮助快速梳理功能架构、核心技术和演示要点;数据库脚本则提供现成的数据表结构,节省建库时间。整套资料对提升毕业设计完成效率和答辩通过率有实际帮助。

1. 基于 Java 的电子书下载系统:一份从数据库到答辩 PPT 的完整毕业设计

拿到这份压缩包的时候,我的第一反应是“又一个老项目”,但解压之后反而有点意外。里面不是孤零零一段源码,而是book.sql数据库脚本、项目报告 doc、book.zip源代码和四张运行截图,等于把毕业设计的实物资产全塞进来了。这个基于 Java 的电子书下载系统,恰恰是很多同学第一次接触真实 Web 项目时会遇到的样子:登录、注册、图书检索、文件下载、后台管理,全都在一个 JSP/Servlet 工程里跑通。

真正跑起来之后,你会发现最值得看的地方不是页面,而是“下载”这个动作。它没有简单地把 PDF 链接暴露出去,而是通过 Servlet 读文件、写响应头、输出字节流,每一步都涉及浏览器的下载协议、文件名编码和服务器路径定位,这些恰恰是答辩时最容易追问的点。所以这份资源适合两类人:一类是在准备 Java 毕业设计、需要一套能演示的完整系统的人;另一类是已经学了 Servlet/JSP,但没见过整条请求链路怎么串起来的人。你可以把它当作一份能拆、能跑、能改的 Java Web 活教材。

2. 项目全景:资源清单、技术栈与源码目录

2.1 压缩包里的四件套:每个文件都不白给

先看资源构成。很多人下载完文件只看源代码,忽略了报告和 SQL,实际上这几类文件是互相补充的。我给这份资源列了一个清单,你对照着看就知道该先打开哪个:

文件作用使用建议
book.sql建库、建表、插入初始化数据用 Navicat 或命令行先建库,推荐第一步看
项目报告 doc项目背景、需求分析、功能设计、数据库设计写论文时直接改章节结构,答辩老师也按这个问
book.zipEclipse/MyEclipse 工程源码,含 src 和 WebContent解压后导入 IDE,不要直接在压缩包里打开
1.JPG、3.jpg、2.JPG、4.jpg前台页面和后台管理的运行截图可以贴进开题报告、答辩 PPT 和论文截图页

这里有个容易被忽视的点:项目报告 doc 里通常附了功能结构图、业务流程图和 ER 图,这些都是从book.sql反推不出来的。我一般会先翻报告里的“功能需求”一节,把模块画出来,再回源码里找对应 Servlet,这样整个系统在你脑子里就不是一团乱麻了。

2.2 技术栈:Servlet/JSP + MySQL 的轻量组合为什么能打

从标题和 SQL 脚本可以看出,这套系统采用典型的 Java Web 传统三层结构,没有引入 Spring Boot。按这类项目的通行做法,技术栈大概是:JDK 8 + Tomcat 8.5 + MySQL 5.7 + Servlet 3.0 + JSP + JSTL,再加上 JDBC 直连和commons-fileupload文件上传组件。数据库驱动一般会放在WEB-INF/lib目录下,你导入工程后能在项目里直接看到mysql-connector-java的 jar 包。

为什么不建议你看到“没有 Spring Boot”就换掉?因为毕业设计答辩重点考察的是你对 Request、Response、Session、Servlet 生命周期的理解。用 Spring Boot 的话,一个注解就完成了请求映射,反而讲不清底层发生了什么。这套老式结构的优势在于“可解释”和“可调试”——任何一个请求,你能从 JSP 页面的 form 表单追踪到 Servlet,再到 DAO 的 SQL,链路非常短。

另外,这个项目不是 Maven 工程,所以不需要联网拉依赖,只要本机装了 JDK、MySQL、Tomcat 和 Eclipse 就能跑。对于答辩现场环境来说,这是最稳妥的配置。如果你非要用 IDEA 打开,选择 “Import Project” 时也要以 Eclipse 工程方式导入,否则会造成.classpath读取失败。

2.3 源码目录:四层分包一眼看懂

解压book.zip后,目录结构比较清晰,常见分包方式如下:

book.zip ├── src │ ├── com.book.bean # 实体类,对应数据库表,如 User、Book、Category │ ├── com.book.dao # JDBC 数据访问层,封装查询、更新方法 │ ├── com.book.servlet # 控制器层,处理页面请求并转发 │ ├── com.book.filter # 过滤器,如登录校验、编码过滤 │ ├── com.book.util # 工具类,如 DBUtil、MD5Util │ └── book.properties # 数据库连接配置 └── WebContent # Web 根目录 ├── admin # 后台管理 JSP,管理员专属页面 ├── css # 全局样式 ├── images # 图书封面、静态图片 ├── files # 上传和存放电子书文件的目录 ├── WEB-INF │ ├── lib # MySQL 驱动、fileupload 等 jar 包 │ └── web.xml # Servlet注册、欢迎页、编码过滤配置 ├── login.jsp # 登录页 ├── register.jsp # 注册页 └── index.jsp # 前台首页,展示图书列表

这个结构的核心思想是“实体-数据访问-控制-展示”四层分离。bean里的字段基本和数据库列名一一对应,dao只负责拼 SQL、执行查询并返回 Java 对象,servlet负责接收 request 参数、调用 dao、再forward或redirect到 JSP,而 JSP 里尽量减少 Java 代码,只在需要遍历列表时使用少量 JSTL 标签。代码中如果出现大量out.println,那通常是早期写法,不影响运行,但可以重构。

这里还要提示一个导入细节:Eclipse 导入时选择File -> Import -> Existing Projects into Workspace,然后选中你解压后的book文件夹。如果导入后 Tomcat 发布时看不到 WebContent 下的内容,可以右键工程选择 Properties,查看Project Facets的 Dynamic Web Module 是否勾选。这个问题在避坑章还会再强调。

3. 数据库核心拆解:book.sql 里的表结构与初始化数据

3.1 四张核心表:从用户到下载记录

电子书下载系统的数据模型并不复杂,从book.sql里可以提炼出的核心表一般包含四张:用户表、图书分类表、图书表、下载记录表。以下是按这类项目最可能出现的表结构整理的建表 SQL,和源码里的book.sql基本对应:

-- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存的是 MD5 密文 nickname VARCHAR(50), role TINYINT DEFAULT 0, -- 0 普通用户,1 管理员 create_time DATETIME ); -- 图书分类表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(50) NOT NULL ); -- 图书表 CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, -- 所属分类 book_name VARCHAR(100), author VARCHAR(50), file_path VARCHAR(255), -- 电子书相对路径,如 /files/java.pdf download_count INT DEFAULT 0, FOREIGN KEY (category_id) REFERENCES t_category(id) ); -- 下载记录表 CREATE TABLE t_download_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, book_id INT, download_time DATETIME );

注意t_book.file_path这个字段,它存的是服务器内部相对路径,而不是外部完整 URL,比如http://localhost:8080/book/files/java.pdf。后端在下载时用getServletContext().getRealPath("/files/java.pdf")把它转换成服务器磁盘上的绝对路径,再写文件流。这个设计保证了换一台电脑部署时,只要保持 WebContent/files 目录不变,路径就不会失效。如果你在 SQL 里看到写死D:\开头的路径,那就要格外小心,换机器后很容易 404。

t_download_record是这套系统能体现“下载行为”的增量表,用户每点击一次下载,这里就会插入一条记录。很多同学在答辩时被问“怎么知道一本书被下载了多少次”,答案就在这张表,配合t_book.download_count可以做到计数缓存和明细留存。

3.2 初始化数据:预置账号和样例书是为了让演示不冷场

book.sql的价值不只是建表,还在于它预置了一批数据。这类毕业设计项目在答辩现场没有太多时间让你手动注册、上传书籍,所以脚本里通常会插入管理员账号、几个分类和几本样例图书。常见插入语句类似:

-- 预置管理员,密码 123456 的 MD5 值 INSERT INTO t_user (username, password, nickname, role) VALUES ('admin', MD5('123456'), '管理员', 1); -- 预置分类 INSERT INTO t_category (cname) VALUES ('Java'), ('Python'), ('前端'); -- 预置图书 INSERT INTO t_book (category_id, book_name, author, file_path, download_count) VALUES (1, 'Java编程思想', '示例作者', '/files/java.pdf', 120), (2, 'Python入门', '示例作者', '/files/python.pdf', 85);

这里最需要留意的点是密码加密。初始化脚本里如果用的是MD5('123456'),那登录代码里也一定要先对用户输入的密码做同样的 MD5 再比对,否则会出现“密码明明没错但登录失败”的诡异问题。如果你在源码里直接拿明文和数据库里的密文比较,那一定是出 bug 的根源。

另外,file_path对应的文件必须真实存在于 WebContent/files 下,否则页面能看到书名,点击下载却会报文件找不到。这个属于避坑章的高频问题,后面再展开。

3.3 数据库连接配置:db.properties 与 JDBC 工具类

老式 Java Web 项目通常使用db.properties保存连接信息,然后在DBUtil静态块里加载属性。配置内容一般长这样:

driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/bookdb?useUnicode=true&characterEncoding=UTF-8 username=root password=root

对应的工具类代码可以简化为:

public class DBUtil { private static String url; private static String username; private static String password; static { try { Properties props = new Properties(); props.load(DBUtil.class.getClassLoader() .getResourceAsStream("db.properties")); url = props.getProperty("url"); username = props.getProperty("username"); password = props.getProperty("password"); Class.forName(props.getProperty("driver")); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws Exception { return DriverManager.getConnection(url, username, password); } }

这里解释几个参数的作用:useUnicode=true表示启用 Unicode 字符集映射,characterEncoding=UTF-8保证中文在 JDBC 传输过程中不变成乱码;driver是 MySQL 5.x 时代的驱动类名,如果你用的是 MySQL 8.x,驱动类名要改成com.mysql.cj.jdbc.Driver,并且 url 里通常还要加serverTimezone=Asia/Shanghai,否则会报时区错误。这是这些年最常踩的版本坑。

getConnection()每次都通过DriverManager创建新连接,没有使用连接池。对于并发很低的毕业设计演示,这样做足够;但如果你打算把它扩成一个小型线上项目,下一步就该引入 Druid 或 HikariCP。答辩时如果老师问“为什么不用连接池”,你可以说这是为了突出 JDBC 基本功,再补一句改进方案,反而能加分。

4. 核心代码实战:从登录鉴权到文件下载的完整链路

4.1 登录与会话:LoginServlet 和 AuthFilter 怎么配合

整个系统的访问控制点是 session。登录成功之后,用户信息被放进 session,后续页面通过过滤器校验 session 是否为空。核心的LoginServlet大致如下:

@WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = MD5Util.md5(req.getParameter("password")); UserDao dao = new UserDao(); User user = dao.findByUsernameAndPassword(username, password); if (user != null) { req.getSession().setAttribute("loginUser", user); resp.sendRedirect(req.getContextPath() + "/index.jsp"); } else { req.setAttribute("msg", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } }

这段代码的逻辑很直白:先拿到表单里的用户名和密码,密码先经过 MD5 加密,再交给 DAO 查询;查询到用户就写入 session,查询不到就回到登录页并提示错误。注意sendRedirect拼接了getContextPath(),这是很多新手会漏掉的一点——如果项目部署名不是根路径而是/book,不带上下文路径会跳转到 404。

再看配合使用的过滤器AuthFilter:

@WebFilter("/*") public class AuthFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String uri = req.getRequestURI(); // 只拦截 JSP,放行静态资源和登录注册页 if (uri.endsWith(".jsp") && !uri.contains("/login.jsp") && !uri.contains("/register.jsp")) { HttpSession session = req.getSession(false); if (session == null || session.getAttribute("loginUser") == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } } chain.doFilter(request, response); } }

过滤器的关键在“拦截范围”和“放行条件”。如果直接拦截/*,那么 css、images、js 全部会被挡在门外,页面会出现“有功能但没样式”的情况。上面这段代码用uri.endsWith(".jsp")把拦截范围收窄到 JSP 动态页面,静态资源不受影响。如果源码里写的是拦截所有路径,那避坑章里提到的样式丢失问题大概率就是这么来的。

4.2 书库列表与搜索:DAO 层用占位符拼 SQL

前台首页最重要的功能是展示图书列表,通常会提供按书名关键字和分类的筛选。一个合格的BookDao里,列表查询方法大概长这样:

public List<Book> list(String keyword, Integer categoryId) { StringBuilder sql = new StringBuilder("SELECT * FROM t_book WHERE 1=1"); List<Object> params = new ArrayList<>(); if (keyword != null && !keyword.trim().isEmpty()) { sql.append(" AND book_name LIKE ?"); params.add("%" + keyword.trim() + "%"); } if (categoryId != null && categoryId > 0) { sql.append(" AND category_id = ?"); params.add(categoryId); } sql.append(" ORDER BY download_count DESC"); // 使用 PreparedStatement 填充 params,得到结果集后封装成 List<Book> }

这里的WHERE 1=1看起来有点随意,实则是为了后续条件拼接方便,避免出现WHERE AND ...这种 SQL 语法错误。值得肯定的是,搜索参数没有直接拼进字符串,而是用?占位符传给PreparedStatement,能有效挡住单引号闭合导致的 SQL 注入。keyword 传入时包装成%关键字%,实现模糊查找;categoryId 为 null 或小于等于 0 时不起作用,可以视为“全部分类”。

排序用download_count DESC,首页默认就把下载次数最多的书往前排。这个字段是下载模块写进来的,所以整个系统的数据流是闭合的——下载行为影响列表展示,列表展示又吸引用户点击下载。

4.3 下载核心:DownloadServlet 的响应头与 IO 流

这个模块是整个电子书下载系统的灵魂,也是答辩时最值得展开讲的代码。它的工作不是简单地跳转到文件 URL,而是通过 Servlet 把文件以流的形式写给浏览器。核心代码如下:

@WebServlet("/download") public class DownloadServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { int bookId = Integer.parseInt(req.getParameter("id")); BookDao dao = new BookDao(); Book book = dao.findById(bookId); // 将相对路径转换为服务器磁盘绝对路径 String realPath = getServletContext().getRealPath(book.getFilePath()); File file = new File(realPath); // 明确响应类型为二进制文件 resp.setContentType("application/octet-stream"); // 中文文件名编码处理 String fileName = URLEncoder .encode(book.getBookName(), "UTF-8") .replaceAll("\\+", "%20"); resp.setHeader("Content-Disposition", "attachment;filename=" + fileName); // 用字节流复制文件 try (InputStream is = new FileInputStream(file); OutputStream os = resp.getOutputStream()) { byte[] buf = new byte[4096]; int len; while ((len = is.read(buf)) != -1) { os.write(buf, 0, len); } } // 下载完成后累加计数 dao.incrementDownloadCount(bookId); } }

这段代码有三个地方容易被追问。第一,getRealPath(book.getFilePath())是把数据库里存的相对路径映射成磁盘真实路径,因为 Tomcat 会把 WebContent 下的文件复制到部署目录,用这个方法才能找到实际文件。第二,Content-Disposition: attachment会强制浏览器弹出“保存”而不打开,如果写成inline则浏览器会尝试预览 PDF,效果完全不同。第三,中文文件名必须先URLEncoder.encode再放进响应头,否则不同浏览器可能显示成乱码;这里把编码后的+替换成%20,是为了避免空格变成加号导致文件名错误。

字节流从输入流读取,再写入输出流,缓冲区buf设为 4096 字节,属于常见写文件流的方式。如果文件较大,可以适当调大缓冲区到 8192 或 16384,减少读写次数。下载完成后dao.incrementDownloadCount(bookId)为列表页的热门排序提供数据,这一步不要漏掉,否则首页的download_count永远是初始化值。

4.4 后台上传:commons-fileupload 解析 multipart 请求

管理员往系统里增加电子书,需要处理表单和文件两种数据。由于 HTML 表单设置了enctype="multipart/form-data",普通的req.getParameter()取不到字段值,必须用commons-fileupload组件解析。关键代码如下:

@WebServlet("/admin/uploadBook") public class UploadBookServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception { DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); // 解析请求体,每个 item 要么是普通字段,要么是文件 List<FileItem> items = upload.parseRequest(req); String bookName = null; String filePath = null; for (FileItem item : items) { if (item.isFormField()) { if ("bookName".equals(item.getFieldName())) { bookName = item.getString("UTF-8"); } } else { // 只取文件名,防止路径穿越 String fileName = new File(item.getName()).getName(); String savePath = getServletContext() .getRealPath("/files/" + fileName); item.write(new File(savePath)); filePath = "/files/" + fileName; } } new BookDao().addBook(bookName, filePath); resp.sendRedirect(req.getContextPath() + "/admin/bookList.jsp"); } }

这里item.isFormField()用来区分普通表单输入和上传文件;普通文本调用getString("UTF-8")能避免中文乱码。文件部分用new File(item.getName()).getName()去掉路径信息,只留下真正的文件名,这是防止用户恶意上传带路径文件名的常规手段。文件被写入WebContent/files目录,数据库里保存/files/文件名这样的相对路径,下载模块再用这个相对路径去定位文件。

使用这个 Servlet 有两个前置条件:WEB-INF/lib下必须有commons-fileupload和commons-io两个 jar 包,并且上传大小限制在web.xml或 Tomcat 配置里不能太小,否则传几十 MB 的 PDF 时会直接抛异常。你如果不想引入组件,也可以用 Servlet 3.0 自带的Part接口,但老工程的 web.xml 版本如果太低,就还得走 fileupload。

5. 避坑指南:运行这套源码最容易翻车的六个问题

5.1 环境配置类:版本、编码与数据库连接

现象一:把项目导入 Eclipse 后,访问 JSP 页面报 “Unable to compile class for JSP” 或者一堆The method getWriter() has already been called之类的错误。

原因:JDK 版本和 Tomcat 版本不匹配,或者项目编译级别设置太高。比如本机是 JDK 17,却用很老版本的 Tomcat 启动,JSP 编译时会直接失败。

解决:尽量用 JDK 8 搭配 Tomcat 8.5,这也是大多数毕业设计项目最稳妥的搭配。在 Eclipse 里右键项目 Properties,把 Java Compiler 的编译级别改成 1.8,再把 Tomcat Runtime 绑定到 8.5。改完后清空 Tomcat 的 work 目录和项目 target,重新发布,基本能解决。

现象二:页面上的中文全是问号,或者数据库里插入的中文在页面上显示乱码。

原因:项目文件编码、JSP 页面 charset、MySQL 表字符集三者没有统一成 UTF-8。

解决:先执行ALTER DATABASE bookdb DEFAULT CHARACTER SET utf8mb4;,再把db.properties里的 url 末尾加上useUnicode=true&characterEncoding=UTF-8。Eclipse 的 Workspace 编码也要改为 UTF-8,JSP 顶部确认有pageEncoding="UTF-8"。这三处都统一后,乱码基本消失。

现象三:Tomcat 启动后控制台报Access denied for user 'root'@'localhost'或者Unknown database 'bookdb'。

原因:数据库脚本没执行,或者book.properties里的用户名密码和你的 MySQL 不一致。

解决:先用命令行或 Navicat 执行source book.sql;,确认生成了bookdb库。然后打开db.properties,把username和password改成你本机的实际账号。如果你用的是 MySQL 8.x,驱动类名要一并改成com.mysql.cj.jdbc.Driver,并在 url 里加serverTimezone=Asia/Shanghai。

5.2 部署与运行类:路径、权限与上传问题

现象四:登录成功跳到首页后,点击任意一本书下载,浏览器提示 404,或者下载下来的文件是 0 字节。

原因:数据库里file_path写的路径在 WebContent/files 下找不到对应文件。最常见的情况是 SQL 脚本里写的是绝对路径,如D:/电子书/java.pdf,而源码包里根本没有这个目录结构。

解决:把file_path统一改成/files/java.pdf这类相对路径,然后确认WebContent/files目录下确实存在同名文件。如果压缩包里没有附带 PDF 样例,可以随便放一个 txt 或 PDF 进去,再把数据库路径改一致,下载链路就能通。

现象五:登录成功后刷新页面又被弹回登录页,同时整个页面没有任何 CSS 样式。

原因:AuthFilter的拦截范围放得太宽,把静态资源也拦截了;或者跳转路径没有带项目上下文。

解决:把过滤器里的放行条件改成:不以.jsp结尾的请求直接放行,以/css/、/images/、/files/开头的路径直接放行。同时把页面里的样式引用写成<%=request.getContextPath()%>/css/style.css这种带上下文路径的形式,而不是相对路径css/style.css。

现象六:管理员上传电子书时,控制台报Unable to process file upload或者类找不到。

原因:缺少commons-fileupload和commons-io的 jar 包,或者 web.xml 的multipart-config没配置上传上限。

解决:确认WEB-INF/lib下有两个 jar,如果没有,从老项目或本地 Maven 仓库里复制一份进去。如果需要限制大小,在web.xml的 Servlet 节点里增加<multipart-config><max-file-size>104857600</max-file-size></multipart-config>,表示最大 100MB。改完后重启 Tomcat,不要只做热发布。

6. 二次开发:把系统升级成带“热门下载”的电子书库

如果想在这套代码上做一点有辨识度的改造,我最推荐先加“热门下载排行榜”。它改动面小,但展示效果明显,而且能踩到 SQL 聚合、分组、排序这些考点。基于现有的下载记录表,一条 SQL 就能拿到排名:

SELECT b.book_name, COUNT(r.id) AS download_cnt FROM t_download_record r JOIN t_book b ON r.book_id = b.id GROUP BY r.book_id, b.book_name ORDER BY download_cnt DESC LIMIT 10;

有了这条 SQL,你可以在BookDao里新增一个findHotBooks()方法,返回一个包含书名和下载次数的列表,然后在首页index.jsp的侧边栏用 JSTL<c:forEach>循环输出,就形成了“热门下载”板块。需要注意的是,排行榜的数据来源是t_download_record,如果这张表一直为空,排行榜就显示不出来。所以改造的同时,要把第 4 章下载模块里的“成功下载后插入一条下载记录”这一步补上,否则榜单永远是空的。

如果还想更进一步,可以把BookDao里的 JDBC 查询替换成 MyBatis。改造思路也很明确:新建mybatis-config.xml和BookMapper.xml,把原来手动写PreparedStatement的地方改成 SQL 映射文件;DBUtil逐步退役,让 SqlSessionFactory 管理连接。这个过程能让你顺带理解框架和原生 JDBC 的关系,答辩时说起“为什么引入 MyBatis”会很有底气。

验收的时候,我习惯列一张自测清单:管理员登录能进后台,普通用户注册后能登录;首页按分类筛选和关键字搜索正常;点击下载后文件能保存且文件名中文不乱码;后台新增一本书后,前台能看到并下载;清空下载记录后,排行榜自动消失。每一项都跑通,整套系统才算真正交付。从那以后,我每次拿到别人的毕设资源,都先看 SQL 脚本和源码里的 lib 目录,再启动 Tomcat,因为十有八九跑不通的问题都出在这两个地方。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进