
简介这是一份基于JSP技术的网上书店系统毕业设计文档面向计算机相关专业的学生及需要完成Web开发课程设计的人员。文档内容覆盖系统需求分析、功能模块划分、数据库结构设计与前端应用开发等核心环节着重说明JSP相较于ASP在安全性、性能、跨平台支持等方面的优势并结合MySQL实现图书展示、在线订购、订单管理等典型电子商务功能。资源总计1个文件为doc格式文档大小约1.09MB下载即可直接查看完整中文摘要、英文摘要、目录及正文内容便于参考论文结构、技术选型说明和实现思路。该资源已有352人学习适合用于毕业设计选题参考、系统设计文档撰写借鉴也可辅助理解JSPMySQL在中小型电商系统中的应用方式。1. 基于JSP的网上书店系统毕业设计里最稳的一条Java Web路线很多人一听到JSP就皱眉觉得这是十几年前的老技术。但如果你面对的是“基于JSP的网上书店系统的设计与实现”这类毕业设计题它反而是最稳妥的一条路。JSP加Servlet加JavaBean加MySQL这一套能在一整个代码里把浏览器请求、Servlet接收、Service处理、数据库读写这条链路完整串起来工作量可控论文也好写答辩时还能现场跑起来演示。它适合两种人一种是Java基础中等、时间紧需要快速拿出能跑能讲的成品另一种是想借这个经典题目把Java Web底子打牢的。网上书店是最典型的“人货单”交易模型登录、商品分页、购物车、订单状态全都能覆盖到足够撑起一篇结构完整的毕业设计。2. 先从设计与选型入手为什么这个系统还要用JSP2.1 JSP在服务端渲染里的真实定位不是过时是不适合大型项目JSP本质上是Servlet的“模板外挂”Tomcat会把JSP翻译成一个Java Servlet再编译执行所以它天生能做服务端渲染页面里直接写HTML再用EL表达式和JSTL把动态数据填进去。对网上书店这种页面少、逻辑偏展示型的系统来说开发效率很高改一个样式局部刷新就能看到效果。这也是为什么这么多年毕业设计里JSP一直没有被淘汰——不是因为前沿而是因为它在“小系统”这个边界内确实好用。做毕业设计不是做大厂项目我见过有人非要证明JSP过时中途迁到Spring Boot结果工期翻倍代码比原来复杂不少。答辩老师通常关心的分点是分层是否清楚、数据库关系是否合理、功能是否完整、有没有自己的设计思考。JSP加Servlet这套组合能全部覆盖。而且这套系统里可以顺带讲清楚几个设计模式的Java实现Filter是责任链Service层是门面DAO层可以抽一个模板方法基类论文的设计部分反而不愁没内容写。2.2 数据库设计六张表把书店的“人货单”闭环网上书店的业务可以拆成“人、货、单”三块用户是“人”图书和分类是“货”订单和订单明细是“单”再加一张评论表作为“货”的延伸反馈。我一般建六张表普通用户和管理员放同一张表用role字段区分这样后台权限控制不用维护两套用户体系代码和论文都省事。下面是可直接落地的建表脚本字符集统一用utf8mb4避免中文乱码和emoji问题。CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT MD5加盐后的密文, role TINYINT NOT NULL DEFAULT 0 COMMENT 0普通用户 1管理员, created_at DATETIME NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, sort_order INT DEFAULT 0 ) COMMENT图书分类表; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, title VARCHAR(100) NOT NULL, author VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, cover_url VARCHAR(255) COMMENT 封面图相对路径, description TEXT, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, created_at DATETIME ) COMMENT图书表; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, status TINYINT DEFAULT 1 COMMENT 1待付款 2待发货 3已发货 4已完成 0已取消, total_amount DECIMAL(10,2) NOT NULL, receiver_name VARCHAR(30), receiver_phone VARCHAR(20), receiver_address VARCHAR(200), created_at DATETIME ) COMMENT订单表; CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, book_id INT NOT NULL, book_title VARCHAR(100) COMMENT 下单时的书名快照, book_price DECIMAL(10,2) COMMENT 下单时的单价快照, quantity INT NOT NULL ) COMMENT订单明细表; CREATE TABLE comment ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, user_id INT NOT NULL, content TEXT, rating TINYINT, created_at DATETIME ) COMMENT图书评论表;orders和order_item为什么要拆成两张表因为一个订单对应多本书如果不拆就得在订单表里存一堆逗号分隔的图书名和数量后面按图书维度统计销量时完全没法查。order_item里存了book_title和book_price快照是因为图书单价随时可能调整订单要保留下单那一刻的价格这是电商表设计的常识。价格用DECIMAL(10,2)而不是floatfloat的二进制误差在金额场景下是雷。order_no字段用时间戳加随机数拼唯一值。外键我建议保留但不加ON DELETE CASCADE——分类删除前先检查该分类下有没有上架图书有就拒绝删除逻辑更干净。2.3 项目骨架三层包结构与JSP页面的分工边界项目结构这块很重要它决定你后面怎么写代码、论文怎么讲“分层设计”。我常用的包结构是这样src/ com.mall.bookstore/ entity/ User.java Book.java Cart.java Order.java OrderItem.java dao/ UserDAO.java BookDAO.java OrderDAO.java BaseDAO.java service/ UserService.java BookService.java OrderService.java servlet/ LoginServlet.java RegisterServlet.java BookListServlet.java CartServlet.java OrderServlet.java UploadServlet.java filter/ EncodingFilter.java LoginFilter.java AdminFilter.java util/ DBUtil.java MD5Util.java WebContent/ index.jsp book_list.jsp book_detail.jsp cart.jsp order.jsp admin/ admin_login.jsp book_manage.jsp order_manage.jsp uploads/ 上传的封面图片目录 css/ js/ images/ WEB-INF/lib/ mysql-connector-java-8.0.x.jar调用链我固定是JSP发请求到ServletServlet收参数、组装对象交给Service做校验和业务处理Service调DAO操作数据库结果放request或session最后转发回JSP渲染。这条链路每一层都能单独讲论文画一张时序图就完事。为什么不让JSP直接连JDBC我见过有同学把几十行查询代码直接写进index.jsp页面是能跑但SQL一改整个页面都要动Tomcat还得重新编译这个JSP排查还特别费劲。改成Servlet负责取数、JSP只负责展示之后页面里最多出现JSTL和EL表达式出问题能立刻定位是取数层还是渲染层这是一个用过就会后悔没早改的教训。3. 核心实现分页书架、购物车和订单三个最值得先写的模块3.1 登录与Session用一个Filter把编码和登录拦截一起收掉登录是第一个要做的模块它牵出SessionSession又是购物车和后台权限的地基。我习惯把请求编码和登录校验放到不同的Filter里保证执行顺序先过编码Filter再过登录拦截。编码用Filter的好处是整个系统的Servlet都不需要重复写setCharacterEncoding。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)); UserService userService new UserService(); User user userService.login(username, password); if (user null) { // 用URL参数带错误码避免重定向后request参数丢失 resp.sendRedirect(login.jsp?error1); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, user); if (user.getRole() 1) { resp.sendRedirect(admin/index.jsp); } else { resp.sendRedirect(index.jsp); } } }密码入库前要做MD5加盐登录时把输入同样处理一遍再比对。有一个容易踩的坑是注册时如果没做加密登录时也不要做两边必须一致否则永远登录不上。这里用sendRedirect而不是forward地址栏会变成目标地址刷新页面时不会重复提交表单登录这种写操作我都用重定向。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); } }登录拦截的Filter是这样写的关键点WebFilter(/private/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpSession session request.getSession(false); User user (User) (session null ? null : session.getAttribute(loginUser)); if (user null) { ((HttpServletResponse) resp).sendRedirect(login.jsp); return; } chain.doFilter(req, resp); } }注意这里用request.getSession(false)如果不存在Session就返回null而不是新建一个。很多同学在这个细节上无所谓结果未登录访问时被拦截还白白创建一个空Session浪费内存。登录拦截的URL范围我放在/private/*下前台公开页面、静态资源都不拦截避免出现死循环重定向。3.2 图书列表分页先算总页数再处理越界与空页网上书店的图书一般几十条起步分页是必做项也是答辩高频考点。我一般写一个PageBeanT封装页码、每页条数、总条数、总页数和当前页数据列表。分页逻辑的重点不在LIMIT而在“页码越界处理”——这个放到第4章避坑里细说先看正确写法。public PageBeanBook pageBooks(int pageNum, int pageSize) { PageBeanBook pb new PageBean(); String countSql SELECT COUNT(*) FROM book WHERE status 1; int total queryInt(countSql); // 先算总页数 int totalPages (total pageSize - 1) / pageSize; if (totalPages 0) totalPages 1; // 空数据时至少保留一页 if (pageNum 1) pageNum 1; // 下边界 if (pageNum totalPages) pageNum totalPages; // 上边界 pb.setPageNum(pageNum); pb.setPageSize(pageSize); pb.setTotal(total); pb.setTotalPages(totalPages); // LIMIT ? OFFSET ?偏移量从0开始 pb.setList(queryList( SELECT * FROM book WHERE status 1 ORDER BY id DESC LIMIT ? OFFSET ?, pageSize, (pageNum - 1) * pageSize)); return pb; }偏移量必须是(pageNum - 1) * pageSize这是分页最容易写错的地方第一页偏移0第二页偏移pageSize不是pageNum。页面上点完页码JSP负责渲染数据用JSTL循环页面里不出现任何Java脚本片段c:forEach items${pageBean.list} varbook div classbook-card img src${pageContext.request.contextPath}${book.coverUrl} alt${book.title} h3a hrefbookDetail?id${book.id}${book.title}/a/h3 p${book.price}/p /div /c:forEach这里${pageContext.request.contextPath}是当前Web应用的上下文根拼在图片地址前面。这是网上书店页面里“图片怎么能正常显示”的第一个关键点后面避坑章节还会展开。有同学想在JSP加载完后自动刷新一次来拉最新数据在head里写meta refresh确实能实现但那会闪一下也可能触发重复GET。分页场景我更建议点击页码时跳bookList?pageN由Servlet重新查一次不要靠JSP自刷新。3.3 购物车实现为什么用Session存购物车而不是建表购物车有两种实现建cart表或者放Session。毕业设计阶段我推荐Session方案原因有三第一省去购物车表和DAO少写不少代码第二购物车的生命周期天然和会话绑定关掉浏览器再打开购物车清空这个行为不需要额外解释第三答辩被追问“购物车数据存哪里”时你能拉开Session和Cookie、内存和持久化的区别来答反而容易加分。如果做“用户在线收藏购物车”再把表补上不迟。public class Cart { private MapInteger, CartItem items new LinkedHashMap(); public void add(Integer bookId, int quantity) { CartItem item items.get(bookId); if (item null) { items.put(bookId, new CartItem(bookId, quantity)); } else { item.setQuantity(item.getQuantity() quantity); } } public void remove(Integer bookId) { items.remove(bookId); } public double getTotalPrice() { return items.values().stream() .mapToDouble(i - i.getPrice() * i.getQuantity()) .sum(); } public ListCartItem getItems() { return new ArrayList(items.values()); } }WebServlet(/addCart) public class AddCartServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { int bookId Integer.parseInt(req.getParameter(bookId)); int quantity Integer.parseInt(req.getParameter(quantity)); HttpSession session req.getSession(); Cart cart (Cart) session.getAttribute(cart); if (cart null) { cart new Cart(); session.setAttribute(cart, cart); } cart.add(bookId, quantity); resp.sendRedirect(cart.jsp); } }Map用LinkedHashMap是为了让购物车列表顺序和点击加入的顺序一致而不是随机顺序。加购接口只做“加进Session”不做库存校验因为购物车是预选动作真正锁定库存要等下单那一刻。前端对quantity要限制在1到99后端也要再校验一次别让用户传个负数或超大数进来。3.4 下单与库存扣减把事务边界画在“创建订单扣库存”上下单是整个系统里最值得展示的“设计”。一次下单涉及三件事插入订单头、插入订单明细、扣减图书库存。这三件事要么全部成功要么全部失败不能出现“订单建了库存没扣”的脏数据。所以JDBC的Connection要手动控制事务代码里commit和rollback的位置我都标清楚了。public Order createOrder(Long userId, Cart cart, String receiverName, String receiverPhone, String receiverAddress) { Connection conn DBUtil.getConnection(); Order order new Order(); try { conn.setAutoCommit(false); order.setOrderNo(BK System.currentTimeMillis()); order.setStatus(OrderStatus.PENDING.getCode()); order.setTotalAmount(cart.getTotalPrice()); // 插入订单头拿到自增ID long orderId orderDAO.insert(conn, order); for (CartItem item : cart.getItems()) { // 先查库存再扣减避免下单后发现买不了 int stock bookDAO.getStock(conn, item.getBookId()); if (stock item.getQuantity()) { throw new BizException(《 item.getTitle() 》库存不足); } int rows bookDAO.deductStock(conn, item.getBookId(), item.getQuantity()); if (rows 0) { throw new BizException(扣减库存失败请重试); } orderItemDAO.insert(conn, orderId, item); } conn.commit(); return order; } catch (Exception e) { conn.rollback(); throw new RuntimeException(下单失败已回滚, e); } finally { conn.setAutoCommit(true); conn.close(); } }先查后扣在并发场景下严格说还有超卖窗口但毕业设计没有并发压测需求。答辩时你可以现场演示事务回滚故意在订单明细里写一本不存在的bookId再下单能看到所有插入都没生效这个演示比嘴上讲十遍“我有事务”都有说服力。orderNo用System.currentTimeMillis()拼一个随机数足够保证唯一。购物车的清空动作放在Controller里下单成功之后执行不要放进事务里。跨浏览器兼容在这个环节也值得提一句下单按钮的click事件只用标准的addEventListener不要写旧版IE才支持的attachEvent。虽然今天少见但评委如果现场用一个非Chrome浏览器打开老系统标准写法能保证大概率正常。4. 常见问题排查Java Web毕业设计最容易踩的五个坑4.1 中文乱码三连JSP、请求参数、数据库连接串各查一遍现象页面标题显示“图书列表”变成“å¾ä¹¦å稔或者登录用户名显示成“?????”。原因三层编码至少有一层不对。JSP文件本身不是UTF-8请求参数没有设置UTF-8JDBC连接串没指定characterEncoding。这三层是串在一起的只改一处不够。解决JSP头部必须写pageEncodingUTF-8并且确认IDE右下角实际保存编码也是UTF-8Filter里写request.setCharacterEncoding(UTF-8)和response.setCharacterEncoding(UTF-8)JDBC连接串补上useUnicodetruecharacterEncodingutf8和serverTimezoneAsia/Shanghai数据库表统一用utf8mb4。注意GET请求的编码在Tomcat 7及以下需要改conf/server.xml里的URIEncodingUTF-8Tomcat 8以上默认就是UTF-8。排查顺序从页面文件到Filter到连接串一层层看别上来就改代码。4.2 JDBC驱动加载失败版本和jar位置往往一起翻车现象Tomcat启动时日志报ClassNotFoundException: com.mysql.cj.jdbc.Driver或者报“Unable to load authentication plugin caching_sha2_password”。原因mysql-connector-java.jar没放进WEB-INF/lib目录或者驱动类名和jar版本不匹配。MySQL 5.x用com.mysql.jdbc.Driver8.x用com.mysql.cj.jdbc.Driver驱动版本换了代码没跟上就会翻车。解决把jar包放到WebContent/WEB-INF/lib下IDE里记得刷新项目。如果用的Maven检查依赖scope不要写成provided因为Tomcat运行时还需要它。确认版本后再看连接串MySQL 8的驱动还要求serverTimezoneAsia/Shanghai否则报时区错误。排查时先看日志第一行异常再用IDE的“打开文件位置”确认jar包是否真正在lib下别一上来就怀疑代码。4.3 图片上传后在JSP页面定位不到先分清URL路径和磁盘路径现象图片上传成功文件在D盘某个目录下页面上的img标签永远裂图刷新也没用。原因浏览器里的src是URL路径不是服务器磁盘路径。你把src写成D:/upload/a.jpg只有本机预览能打开换一台电脑或换一个浏览器就失效。网上书店的封面图如果这样存部署到实验室服务器上基本全裂。解决把图片存到WebContent/uploads目录下JSP里用${pageContext.request.contextPath}/uploads/xxx.jpg访问上传时给文件名加UUID前缀避免同名文件互相覆盖。有人会问“jsp图片如何对坐标定位”那其实是另一个层面的问题等src能加载出图之后用CSS的position、left、top可以调整坐标位置但前提永远是图片先能被请求到。路径问题是底层坐标是上层先把路径做对。4.4 分页页码越界删除最后一页数据后翻页白屏现象图书列表有10页管理员删掉第10页的全部数据后用户再点“下一页”或直接输入page10页面空白。原因后端没有校验当前页码是否大于总页数SQL执行LIMIT 10 OFFSET 90返回空集合JSP循环输出为空页面看起来就是白屏。解决查询前先算总页数再把传入的pageNum夹到合法区间pageNum 1就取1pageNum totalPages就取totalPages。这个逻辑在PageBean里做不在Servlet里做因为所有用到分页的模块都要共享。答辩现场这个坑特别好演示现场打开第3页删除一条数据再刷新如果系统能自动回落到第2页这就是一个看得到的小亮点。4.5 改完JSP不生效是缓存不是玄学现象改了index.jsp的标题强刷后还是旧内容过一会又好了或者改了Servlet重启Tomcat后还是旧逻辑。原因两种缓存叠加。浏览器端JSP和HTML页面被缓存没真正拉到新内容Tomcat端改了JSP会触发重新编译但WEB-INF/classes下的class文件默认不自动热加载需要重启。解决开发期用CtrlF5强刷或者直接关闭浏览器缓存选项Tomcat的debug配置reloadabletrue对部分class有热加载能力但不要依赖它。最可靠的是改完class后重启Tomcat然后看logs/catalina.out里的启动日志。血泪经验是JSP页面里不要写大段Java代码一旦JSP编译失败Tomcat的报错信息会指向容器生成的Servlet源码位置又长又乱排查非常痛苦。页面只放JSTL和EL一切逻辑后移这个习惯能少走很多弯路。5. 后台管理与订单闭环让系统从“会跑”变成“能交”5.1 后台权限区分一个role字段加一个后台Filter很多网上书店系统做到用户下单就停了后台只是管理员登进去看一眼。我建议把后台做成真正能操作的管理模块这是拉开差距的地方。user表里已经有role字段注册接口固定写入0管理员账号通过初始化SQL插入然后给/admin/*目录配置一个AdminFilter普通用户就算猜到URL也进不去。WebFilter(/admin/*) public class AdminFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpSession session request.getSession(false); User user (User) (session null ? null : session.getAttribute(loginUser)); if (user null || user.getRole() ! 1) { ((HttpServletResponse) resp).sendRedirect(../login.jsp?error3); return; } chain.doFilter(req, resp); } }拦截路径只写/admin/*前台页面不受影响。注意重定向地址写的是../login.jsp因为浏览器相对路径的基准是当前URL的/admin/目录不带..会跳到不存在的地方。这里同样用getSession(false)未登录时不要创建一个无效Session。管理员账号我建议在init.sql里固定插入一条用户名admin密码MD5加盐后的值注册功能永远注册不出管理员这个边界要讲清楚。5.2 商品上架与图片上传磁盘存储和虚拟路径映射怎么配后台图书管理至少要能录入书名、作者、分类、单价、库存还要能上传封面图。用Servlet 3.0的Part接口就可以实现不用引第三方jar包。核心逻辑是接收文件、重命名、存到web应用的uploads目录、把相对路径存入数据库。WebServlet(/admin/uploadCover) MultipartConfig(maxFileSize 5 * 1024 * 1024) public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part part req.getPart(cover); if (part null || part.getSize() 0) { resp.sendRedirect(book_edit.jsp?error1); return; } String original Paths.get(part.getSubmittedFileName()).getFileName().toString(); String ext original.contains(.) ? original.substring(original.lastIndexOf(.)) : .jpg; // UUID重命名避免中文文件名和重名覆盖 String fileName UUID.randomUUID().toString().replace(-, ) ext; String uploadDir getServletContext().getRealPath(/uploads); File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } part.write(uploadDir File.separator fileName); // 数据库只存相对路径不存绝对路径 Book book (Book) req.getSession().getAttribute(editBook); book.setCoverUrl(uploads/ fileName); resp.sendRedirect(book_edit.jsp?uploadok); } }getRealPath(/uploads)拿到的是Tomcat解压后的Web应用目录开发阶段可以直接写文件。数据库里存的是“uploads/文件名”这个相对路径JSP页面用上下文根拼接就能访问这就是第4章那个图片定位问题的正确做法。MultipartConfig的maxFileSize限制5MB防止大图拖慢页面。跨平台换目录时用File.separator拼路径不要写死D:\这种绝对路径否则部署到Linux服务器直接抛目录不存在。5.3 订单状态流转用枚举代替散落的if判断订单模块最出彩的设计是状态机。不要在Service里到处写if(status 1)这样散落的数字比较一旦状态变多改一处漏一处。我习惯用一个枚举把状态码和描述绑在一起然后维护一张状态流转表控制每个操作只能从特定状态跳转到特定状态。public enum OrderStatus { PENDING(1, 待付款), PAID(2, 待发货), SHIPPED(3, 已发货), COMPLETED(4, 已完成), CANCELLED(0, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } }状态流转的规则可以整理成一张表放在代码注释和论文里都很清晰当前状态允许的操作下一个状态待付款取消订单已取消待付款支付订单待发货待发货发货已发货已发货确认收货已完成已取消无终态已完成无终态Service层的方法开头先取当前订单状态判断是否允许本次操作不允许就抛业务异常。这套逻辑虽然简单但已经是最基本的工作流引擎状态机模型。答辩如果被追问“状态怎么防止乱跳”你能讲出“终态、操作前置校验”这两个概念深度就超过一般毕业设计了。6. 做到这里再往前走一步答辩演示和迁往Spring Boot的路线6.1 答辩前的三个验证换浏览器、断外网、重置数据库系统写完答辩翻车大多发生在现场环境。我每次演示前固定按顺序过三关第一关换浏览器只用Chrome不够至少再用Edge或Firefox把登录、分页、下单、后台管理各走一遍确保没有Chrome专属的写法。第二关断外网WiFi断开再跑一遍完整下单流程确认系统没有依赖外部CDN的jQuery或字体库。很多同学把jQuery从CDN引入现场没网页面直接白板这个锅背得很冤。第三关重置数据库准备一个init.sql一键建库建表插入管理员、十本图书和一个测试订单答辩前一晚从零执行一遍第二天现场用干净环境重新初始化。这个初始化脚本就是后悔药万一现场数据被演示弄乱了恢复也就一分钟的事。6.2 从JSP迁到Spring Boot Vue一张表看懂对应关系如果答辩完还打算继续用这个项目找工作或者做进阶版我建议按下面的对应关系往Spring Boot方向迁而不是推倒重来。你现在做的JSP版不是白做所有业务逻辑都能平移。现在的层JSP版实现Spring Boot Vue版的对应位置页面层JSP JSTL ELVue页面axios调接口控制层ServletRestController / Controller业务层Service类Service方法加Transactional数据层DAO手写JDBCMapper接口 MyBatis Plus会话HttpSessionToken / Redis会话数据传递request session转发JSON 前后端分离迁移时先从DAO换到Mapper编译通过再换Controller最后拆页面。每拆一层就编译运行一次不要一次性推倒重写。最后说一个我自己的教训第一版系统图省事把所有图书列表和详情逻辑都写在JSP页面里后来要加一个按价格排序的功能改了JSP又漏改DAO现场演示排序按钮没反应那个时刻确实尴尬。后来所有页面都改成Servlet收数据、JSP只渲染再加功能就只是加一个Servlet方法的事。技术选型不必追新但分层这件事一定要做对这一条对毕业设计和以后的工作都适用。希望帮到你。本文还有配套的精品资源点击获取