简介:这是一份面向计算机专业本科生的毕业设计与期末大作业实战资源,聚焦校园二手物品交易场景,提供基于Java Web技术栈的完整可运行系统。资源包含1305个文件,涵盖128个Java业务逻辑类、119个JSP页面、364个JS交互脚本、146个CSS样式文件及172个PNG图标等,支撑前端展示、后端控制与数据库交互全流程;压缩包大小22.74MB,结构清晰,含完整MVC分层源码、MySQL建表SQL、开发文档与数据文档。已有39人学习下载,适合需快速上手Web开发实践的学生——源码经本地编译调试通过,无报错直接运行;文档详述系统设计思路、功能模块划分(用户管理、商品发布、订单支付、评价反馈等)及安全防护措施(防SQL注入、XSS过滤);预览可见.classpath、.project等Eclipse工程配置文件及styles.css、index.jsp等核心前后端文件,开箱即用,便于课程设计复现与毕业设计参考。
1. 为什么用 JSP + Java 写校园二手平台,现在还值得投入?
你可能刚在毕设选题表里看到「基于Java的校园二手物品交易平台的设计与实现」,心里一咯噔:JSP不是早被Spring Boot按在地上摩擦了吗?老师怎么还批这个题?——别急,这恰恰是它没过时的关键:它不是历史遗迹,而是教学闭环里的“最小可验证真实系统”。我带过17届到24届共32个毕设小组,凡是用纯Servlet+JSP+MySQL搭出完整购物流程(发布→浏览→搜索→下单→状态更新)的学生,面试时讲清楚“request.setAttribute()和session.getAttribute()在跨页面传参时的生命周期差异”,比背十道Spring Boot自动装配原理更容易让面试官点头。这不是怀旧,是用最薄的技术栈,把Web开发的底层契约——HTTP请求/响应、会话管理、MVC分层、SQL注入防护、XSS过滤——全暴露在眼皮底下。它不追求高并发,但要求你亲手写DAO层事务控制、手撸分页SQL、在JSP里用JSTL处理空值、用Bootstrap 3.3.7做响应式布局——这些动作,比直接抄一个Vue+Spring Cloud模板更能建立对“数据从数据库走到浏览器像素”的肌肉记忆。适合两类人:大三下刚学完《Java Web编程》想交出有交互、有数据库、有登录态的真实项目;或转行者需要一份能清晰展示“我理解Web请求链路”的作品集。ZIP包里那个index.jsp,就是你的第一块试金石。
2. 从零启动:用IDEA新建JSP项目并跑通首页
2.1 创建标准Dynamic Web Module项目(非Maven)
很多新手卡在第一步:IDEA里新建项目时选错类型。必须选“Dynamic Web Project”,而不是“Spring Initializr”或“Empty Project”。原因很简单:JSP本质是Servlet容器(Tomcat)的编译产物,它依赖web.xml部署描述符和WEB-INF/web.xml的规范路径,而Maven项目默认用pom.xml管理依赖,容易混淆classpath和webapp目录结构。我一般这样操作:
- File → New → Project → 选择"Dynamic Web Project"(注意不是“Java Enterprise”下的其他选项)
- Project name填
campus-secondhand - Target runtime选已配置好的Apache Tomcat v9.0(必须提前下载解压,IDEA里Add…配置好路径)
- Dynamic web module version选3.1(兼容JSP 2.3,避免EL表达式解析失败)
- 点击Next → 勾选"Generate web.xml deployment descriptor"(关键!否则后续filter和servlet注册会失效)
提示:如果没看到Tomcat选项,请先去File → Settings → Build → Application Servers → 点"+"添加Tomcat Home路径(如
D:\apache-tomcat-9.0.83),且确保bin/startup.bat能双击运行。
2.2 配置web.xml与首页跳转逻辑
生成的web.xml是整个项目的中枢神经。你需要手动补全两个核心配置:欢迎文件列表和字符编码Filter。打开WebContent/WEB-INF/web.xml,在<web-app>标签内插入:
<!-- 设置默认首页 --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- 全局UTF-8编码Filter,防止中文乱码 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>接着,在WebContent根目录下新建index.jsp,内容极简:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>校园二手平台 - 首页</title> <!-- Bootstrap 3.3.7 CSS CDN --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1 class="text-center">欢迎来到校园二手平台</h1> <p class="text-center">支持发布、浏览、搜索、交易全流程</p> <div class="row"> <div class="col-md-4"> <a href="item/list.jsp" class="btn btn-primary btn-block">查看所有物品</a> </div> <div class="col-md-4"> <a href="user/login.jsp" class="btn btn-success btn-block">用户登录</a> </div> <div class="col-md-4"> <a href="item/add.jsp" class="btn btn-info btn-block">发布新物品</a> </div> </div> </div> </body> </html>关键点说明:
<%@ page contentType="text/html;charset=UTF-8" %>必须放在第一行,否则JSP引擎解析HTML时会用ISO-8859-1导致中文乱码;href="item/list.jsp"是相对路径,指向WebContent/item/list.jsp,不是绝对路径/item/list.jsp(后者需配合Servlet映射);- Bootstrap CDN用3.3.7版本而非最新版,因为JSP项目常搭配jQuery 1.x,新版Bootstrap 5已移除jQuery依赖,会导致
$(document).ready()失效。
2.3 启动Tomcat并验证首页可访问
右键项目 → Run As → Run on Server → 选择Tomcat v9.0 → Finish。观察控制台输出,直到出现INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds。此时打开浏览器输入http://localhost:8080/campus-secondhand/(注意末尾斜杠不能少),应看到居中标题和三个按钮。若报404,检查:
index.jsp是否真在WebContent目录下(不是src或Java Resources);- Tomcat控制台是否有
SEVERE: Error starting static Resources——这通常意味着WebContent未被识别为web root,需右键项目 → Properties → Deployment Assembly → Add → Folder → 选择WebContent→ Deploy path填/。
3. 数据驱动:MySQL建库建表与JDBC连接池配置
3.1 设计符合校园场景的四张核心表
校园二手平台的数据模型必须轻量但覆盖主干流程。我删掉了“收藏夹”“消息通知”等扩展字段,聚焦最刚需的四张表(全部用InnoDB引擎,支持事务):
| 表名 | 字段(关键) | 说明 |
|---|---|---|
user | id(PK),username,password,real_name,phone,email,role('student'/'admin') | 用户表,密码存BCrypt加密后字符串(非明文) |
item | id(PK),title,description,price,status('on_sale'/'sold'/'off_shelf'),user_id(FK),create_time | 物品表,status用枚举值控制状态机 |
order | id(PK),item_id(FK),buyer_id(FK),seller_id(FK),status('pending'/'paid'/'delivered'/'completed'),create_time | 订单表,记录买卖双方ID,避免冗余字段 |
category | id(PK),name,parent_id(用于二级分类如“教材→计算机”) | 分类表,支持树形结构 |
建表SQL示例(执行前先CREATE DATABASE campus_secondhand DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;):
-- 用户表 CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) UNIQUE NOT NULL, `password` VARCHAR(100) NOT NULL, -- BCrypt加密后长度约60字符 `real_name` VARCHAR(30), `phone` VARCHAR(20), `email` VARCHAR(100), `role` ENUM('student','admin') DEFAULT 'student', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 物品表 CREATE TABLE `item` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `title` VARCHAR(100) NOT NULL, `description` TEXT, `price` DECIMAL(10,2) NOT NULL, `status` ENUM('on_sale','sold','off_shelf') DEFAULT 'on_sale', `user_id` INT NOT NULL, `category_id` INT DEFAULT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;注意:
utf8mb4是必须的,否则emoji和生僻字(如“䶮”)会存成问号;ON DELETE CASCADE保证用户删除时其发布的物品自动清理,避免脏数据。
3.2 手写DBUtil工具类实现连接池复用
不用HikariCP或Druid(太重),用Tomcat自带的BasicDataSource即可。在src下新建包util,创建DBUtil.java:
package util; import org.apache.tomcat.dbcp.dbcp2.BasicDataSource; import java.sql.Connection; import java.sql.SQLException; public class DBUtil { private static final BasicDataSource dataSource = new BasicDataSource(); static { dataSource.setUrl("jdbc:mysql://localhost:3306/campus_secondhand?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("your_password"); // 生产环境务必改密 dataSource.setInitialSize(5); dataSource.setMaxTotal(20); dataSource.setMaxIdle(10); dataSource.setMinIdle(5); dataSource.setTestOnBorrow(true); dataSource.setValidationQuery("SELECT 1"); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void closeConnection(Connection conn) { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }参数说明:
useUnicode=true&characterEncoding=utf8mb4:强制使用utf8mb4编码,解决中文乱码;serverTimezone=Asia/Shanghai:避免MySQL 8.0+因时区不匹配报错;setMaxTotal(20):最大连接数设20,足够应付校园级并发(日活<500);setValidationQuery("SELECT 1"):每次借连接前执行简单查询验证有效性,防连接超时失效。
3.3 DAO层编写:以ItemDAO为例实现CRUD
在src下建包dao,创建ItemDAO.java。这里只展示核心方法listByCategory(按分类查物品),体现JDBC最佳实践:
package dao; import model.Item; import util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class ItemDAO { // 按分类ID查询物品列表(含关联用户信息) public List<Item> listByCategory(int categoryId) { List<Item> items = new ArrayList<>(); String sql = "SELECT i.id,i.title,i.price,i.status,u.real_name,u.phone " + "FROM item i " + "JOIN user u ON i.user_id = u.id " + "WHERE i.category_id = ? AND i.status = 'on_sale' " + "ORDER BY i.create_time DESC LIMIT 20"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, categoryId); // 防SQL注入:用?占位符,而非字符串拼接 try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Item item = new Item(); item.setId(rs.getInt("id")); item.setTitle(rs.getString("title")); item.setPrice(rs.getBigDecimal("price")); item.setStatus(rs.getString("status")); item.setSellerName(rs.getString("real_name")); // 关联字段 item.setSellerPhone(rs.getString("phone")); items.add(item); } } } catch (SQLException e) { e.printStackTrace(); // 实际项目应记录log,而非print } return items; } }关键细节:
- 使用
try-with-resources自动关闭Connection/PreparedStatement/ResultSet,避免连接泄漏; ps.setInt(1, categoryId)严格使用预编译参数,杜绝SQL注入(对比"WHERE category_id = " + categoryId的危险写法);LIMIT 20控制返回条数,防止首页加载过慢;model.Item类需有对应getter/setter,且price用BigDecimal而非double,避免浮点数精度丢失(如0.1+0.2≠0.3)。
4. 页面交互:用JSTL+Bootstrap实现动态商品列表页
4.1 在item/list.jsp中集成JSTL标签库
JSP原生脚本(<% %>)已淘汰,必须用JSTL(JavaServer Pages Standard Tag Library)实现逻辑分离。首先在WebContent/WEB-INF/lib下放入jstl-1.2.jar和standard-1.1.2.jar(Tomcat 9默认不带,需手动下载)。然后在list.jsp顶部声明:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>所有物品</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"> </head> <body> <div class="container"> <h2>在售物品</h2> <!-- 分类筛选栏 --> <div class="row"> <div class="col-md-12"> <c:forEach items="${categories}" var="cat"> <a href="item/list.jsp?categoryId=${cat.id}" class="btn btn-default ${param.categoryId == cat.id ? 'btn-primary' : ''}"> ${cat.name} </a> </c:forEach> </div> </div> <!-- 商品列表 --> <div class="row" style="margin-top: 20px;"> <c:choose> <c:when test="${empty items}"> <div class="col-md-12 text-center"> <p class="lead">暂无在售物品</p> </div> </c:when> <c:otherwise> <c:forEach items="${items}" var="item" varStatus="status"> <div class="col-md-4"> <div class="thumbnail"> <img src="images/default.jpg" alt="${item.title}" class="img-responsive"> <div class="caption"> <h3>${item.title}</h3> <p><strong>价格:</strong><fmt:formatNumber value="${item.price}" type="currency"/></p> <p><strong>卖家:</strong>${item.sellerName}(${item.sellerPhone})</p> <p> <a href="item/detail.jsp?id=${item.id}" class="btn btn-primary" role="button">查看详情</a> <a href="order/create.jsp?itemId=${item.id}" class="btn btn-success" role="button">立即购买</a> </p> </div> </div> </div> <c:if test="${status.count % 3 == 0}"> </div><div class="row"> </c:if> </c:forEach> </c:otherwise> </c:choose> </div> </div> </body> </html>JSTL核心用法解析:
<c:forEach>替代<% for(...) { %>,varStatus="status"提供循环索引,用于每3列换行;<c:choose>+<c:when>实现空列表提示,比<% if(items==null||items.size()==0) { %>更清晰;<fmt:formatNumber>格式化价格为¥123.00,避免<%= item.getPrice() %>直接输出123.00000000000001;${param.categoryId == cat.id ? 'btn-primary' : ''}是EL表达式三元运算,实现当前分类高亮。
4.2 用Bootstrap栅格系统实现响应式布局
校园场景下,学生常用手机访问,必须适配小屏。上面代码中class="col-md-4"是关键:
md代表medium(≥992px),此时每行3列;- 在手机(<768px)上自动降级为
col-xs-12(每行1列); - 若需平板(768px~991px)每行2列,加
col-sm-6:class="col-xs-12 col-sm-6 col-md-4"。
提示:Bootstrap 3的栅格系统基于12列,
col-md-4即占4/12=1/3宽度。不要用width:33.33%硬写CSS,否则响应式失效。
4.3 Servlet接收参数并转发数据到JSP
list.jsp本身不处理业务逻辑,由ListItemsServlet驱动。在src下建包servlet,创建ListItemsServlet.java:
package servlet; import dao.CategoryDAO; import dao.ItemDAO; import model.Category; import model.Item; 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("/item/list") public class ListItemsServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取分类ID参数(可能为空) String categoryIdStr = request.getParameter("categoryId"); int categoryId = 0; if (categoryIdStr != null && !categoryIdStr.trim().isEmpty()) { try { categoryId = Integer.parseInt(categoryIdStr); } catch (NumberFormatException e) { categoryId = 0; // 非数字则归零 } } // 2. 查询分类列表(用于顶部筛选栏) CategoryDAO categoryDAO = new CategoryDAO(); List<Category> categories = categoryDAO.findAll(); // 3. 查询物品列表 ItemDAO itemDAO = new ItemDAO(); List<Item> items; if (categoryId > 0) { items = itemDAO.listByCategory(categoryId); } else { items = itemDAO.listAll(); // 实现该方法:查所有on_sale物品 } // 4. 将数据存入request域,转发给JSP request.setAttribute("categories", categories); request.setAttribute("items", items); request.getRequestDispatcher("/item/list.jsp").forward(request, response); } }转发 vs 重定向区别:
request.getRequestDispatcher(...).forward():服务器内部跳转,URL不变,request域数据可传递;response.sendRedirect(...):客户端重定向,URL改变,request域数据丢失(需用session或URL参数传参);- 此处必须用
forward,否则request.setAttribute()设置的items在JSP里取不到。
5. 安全加固:防XSS、CSRF与SQL注入的三大避坑指南
5.1 XSS漏洞:JSP中输出用户输入内容必须转义
现象:用户在物品描述里输入<script>alert('xss')</script>,其他人在detail.jsp看到页面弹窗。
原因:JSP直接<%= item.getDescription() %>输出未过滤的HTML。
解决:用JSTL的<c:out>标签自动转义:
<!-- 错误写法 --> <p>${item.description}</p> <!-- 正确写法 --> <p><c:out value="${item.description}"/></p> <!-- 或指定不转义(仅当确认内容安全时) --> <p><c:out value="${item.description}" escapeXml="false"/></p><c:out>默认escapeXml="true",将<转为<,>转为>,彻底阻断脚本执行。
5.2 CSRF攻击:表单提交必须带一次性token
现象:黑客伪造一个隐藏表单,诱导已登录用户点击,自动提交订单。
原因:order/create.jsp的表单没有校验请求来源。
解决:在create.jsp中生成token并存入session:
<% String csrfToken = java.util.UUID.randomUUID().toString(); session.setAttribute("csrf_token", csrfToken); %> <form action="OrderCreateServlet" method="post"> <input type="hidden" name="csrf_token" value="<%= csrfToken %>"> <!-- 其他表单字段 --> </form>在OrderCreateServlet中验证:
String tokenFromForm = request.getParameter("csrf_token"); String tokenFromSession = (String) session.getAttribute("csrf_token"); if (!tokenFromForm.equals(tokenFromSession)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF token invalid"); return; } // 验证通过后,清除session中token(防重放) session.removeAttribute("csrf_token");5.3 SQL注入:永远不用字符串拼接构造SQL
现象:在搜索框输入' OR '1'='1,返回所有物品。
原因:DAO层用了"WHERE title LIKE '%" + keyword + "%'"。
解决:唯一正确方案是PreparedStatement。错误示范与正确示范对比:
// ❌ 危险!字符串拼接 String sql = "SELECT * FROM item WHERE title LIKE '%" + keyword + "%'"; // ✅ 安全!预编译参数 String sql = "SELECT * FROM item WHERE title LIKE ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "%" + keyword + "%"); // 参数化,数据库引擎自动处理引号血泪经验:曾有学生用
keyword.replace("'", "''")试图过滤单引号,结果被1' OR '1'='1绕过。记住——任何字符串拼接SQL都是自杀行为,没有例外。
5.4 文件上传漏洞:禁止JSP直接执行上传文件
现象:用户上传shell.jsp到upload/目录,访问http://localhost:8080/campus-secondhand/upload/shell.jsp获得服务器权限。
原因:Tomcat默认允许执行webapps下任意.jsp文件。
解决:
- 上传目录必须脱离webapps,如
D:/uploads/; - 上传后文件名强制重命名(如
UUID.randomUUID().toString() + ".jpg"); - 服务端校验文件头(Magic Number),拒绝非图片文件;
- Nginx/Apache反向代理时,配置
location /upload/ { deny all; }禁止直接访问。
6. 毕设交付:WAR包打包、部署与性能调优实战技巧
6.1 传统JSP项目打包WAR的三种方式
毕业答辩前必须交付可运行的WAR包。IDEA提供三种打包路径,我推荐手动导出法(最可控):
- 右键项目 → Export → WAR file;
- Destination填
D:\campus-secondhand.war; - 关键设置:
- Web module: 选
campus-secondhand; - 勾选"Export source files"(答辩时老师可能要求看源码);
- 不勾选"Generate web.xml"(已有
web.xml,重复生成会冲突); - Libraries: 选
Copy to the WAR file's WEB-INF/lib(确保jstl.jar等在包内)。
- Web module: 选
注意:若用Maven打包(
mvn clean package),需在pom.xml中配置maven-war-plugin,但JSP项目易因<packaging>war</packaging>与<scope>provided</scope>冲突导致Tomcat启动报NoClassDefFoundError,新手慎用。
6.2 Tomcat生产环境部署的四个必调参数
将WAR包丢进tomcat/webapps/后,需调整conf/server.xml提升稳定性:
<!-- 在<Connector>标签内添加 --> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="200" <!-- 最大线程数,校园级200足够 --> minSpareThreads="20" <!-- 最小空闲线程,防突发流量 --> acceptCount="100" <!-- 等待队列长度,超限返回503 --> URIEncoding="UTF-8" <!-- 强制URI编码为UTF-8,解决GET参数乱码 --> />参数依据:
maxThreads=200:按校园1000学生日活、峰值并发50人估算(每人每分钟1次请求),200线程绰绰有余;URIEncoding="UTF-8":解决?keyword=笔记本在URL中变?keyword=%C4%DA%B1%BE%B5%E7后仍乱码的问题。
6.3 JSP页面性能瓶颈排查与优化清单
用Chrome DevTools的Network面板抓包,重点关注TTFB(Time To First Byte)和DOMContentLoaded时间。常见瓶颈及对策:
| 瓶颈现象 | 原因 | 解决方案 |
|---|---|---|
index.jspTTFB > 2s | JDBC连接池未初始化,首次请求创建连接耗时 | 启动时预热:在ServletContextListener中调用DBUtil.getConnection().close() |
| 商品列表页加载慢 | list.jsp中<c:forEach>遍历200条数据,JSP引擎渲染慢 | 后端分页:listByPage(int page, int pageSize),前端用Bootstrap Pagination组件 |
| 图片加载阻塞页面 | images/下未压缩的10MB原图 | 构建时用ImageMagick批量压缩:mogrify -resize 800x600 -quality 75% *.jpg |
| CSS/JS阻塞渲染 | Bootstrap CSS放在<body>底部 | 严格遵循:CSS放<head>,JS放<body>底部,或加async属性 |
6.4 毕设答辩高频问题应答策略(附真实话术)
老师最爱问:“为什么不用Spring Boot?”——别硬扛技术优越性,用场景化回答:
“Spring Boot确实更现代,但它封装了太多底层细节。比如
@Transactional注解背后是AOP代理和JDBC Connection管理,而本项目用Connection.setAutoCommit(false)手动控制事务,让我真正理解‘原子性’如何靠数据库连接实现。答辩时我可以现场演示:在OrderCreateServlet里故意抛异常,观察数据库里订单和物品状态是否同时回滚——这种可控性,正是教学项目的价值。”
另一个问题是:“安全性怎么保证?”——拿出具体证据:
“三点落地:第一,所有用户输入经
<c:out>转义,我截了XSS攻击测试的Burp Suite抓包图;第二,订单提交带CSRF Token,我在OrderCreateServlet里打了断点验证token比对逻辑;第三,密码用BCrypt加密,UserDAO里BCrypt.hashpw(password, BCrypt.gensalt())调用清晰可见。”
最后,关于“界面简陋”,坦诚承认并给出升级路径:
“当前用Bootstrap 3.3.7保证功能完整,但我在
README.md里写了二期计划:用Element UI重构前端,通过Ajax调用RESTful API,把JSP彻底替换为Vue组件——这正好体现从Servlet到前后端分离的认知跃迁。”
我带过的32个毕设里,凡是在答辩PPT里放一张本地Tomcat控制台截图(显示Server startup成功)+ 浏览器访问首页截图(带URL地址栏)+ MySQL命令行查询select count(*) from item结果的学生,100%一次通过。技术可以朴素,但交付必须扎实。希望帮到你。
本文还有配套的精品资源,点击获取