茗茶文化网站挂上"JSP"这个技术标签,老JavaWeb人应该一眼就能看穿它的全貌:前台茶叶展示加购物车,后台分类管理加内容维护,数据库用MySQL,跑在Tomcat上,典型的课程设计项目。项目包里那串"qf7a3"编号不用管,它只是资源入库时的识别码,真正值钱的是后面跟着的"程序+源码+数据库+调试部署+开发环境"这一整套交付清单。尤其对课设新手来说,代码能看懂一部分,但环境怎么配、SQL怎么导、部署起来为什么老报错,几乎能劝退一大半人。这篇我就拿"茗茶"这个具体项目当例子,把整条链路从头到尾捋一遍:环境版本怎么选、表结构怎么设计、核心代码怎么分层、最后怎么把war包丢进Tomcat跑起来,全部按实操来。适合正在做JavaWeb课设、或者刚拿到一个现成JSP项目还不会跑的同学。
1. 项目全貌与选题价值分析
1.1 一个茶文化网站其实包了哪些功能
"茗茶"是文化外壳,剥开之后就是经典的"前台展示+后台管理"双端结构。前台面向普通访客,包括首页轮播与推荐位、按茶叶分类浏览(绿茶、红茶、乌龙茶、黑茶、白茶、花茶)、茶叶详情页展示产地与工艺、关键词搜索、用户注册登录、购物车与订单提交,以及个人中心里的订单记录和信息展示页面。后台面向管理员,包括管理员登录、分类管理、茶叶信息管理、库存调整、图片上传、订单处理和留言审核。
这个功能清单看起来多,但底层逻辑非常统一:前台是"查",后台是"增删改"。用户从浏览到下单再到查看订单,管理者从登录到维护商品再到处理订单,两条主线能闭环,这个课设项目就算立住了。答辩时老师最看重的不是功能数量,而是这条闭环链路能否走通,以及你能否讲清楚数据在表与表之间是怎么流转的。
1.2 为什么课设/毕设选JSP这套"老"技术栈
现在Spring Boot已经是主流,但JSP项目在课设和本科毕设里依然大量存在,原因很实在:它恰好覆盖了JavaWeb课程的核心知识点。JSP脚本和指令、Servlet生命周期、JDBC数据库访问、Session状态管理、Filter过滤器、Request/Response转发与重定向,一个项目全部包含,想漏都漏不掉。对初学者来说这套技术栈也足够轻,不依赖复杂的构建工具和框架概念,装一个Tomcat加一个MySQL就能跑起来。
部署方式也非常直观:war包丢进webapps目录就能被访问,不像Spring Boot那样需要额外理解内嵌容器和自动配置机制。如果答辩老师问你"为什么不用Spring Boot",你可以理直气壮地回答:这个项目做的是JavaWeb底层机制的学习,Spring Boot封装太多,不利于展示Servlet和JSP的运行原理。这个回答是能自圆其说的,而且在面试场景里反而是加分项,因为很多人只会用框架,底层原理一问就懵。
2. 开发环境搭建与版本选型
2.1 四个核心组件的版本怎么选
环境版本是JSP项目第一个大坑,很多人项目跑不起来,八成是JDK、Tomcat、MySQL的版本互相不兼容。我踩过的组合基本稳定在一套老配方上:
| 组件 | 建议版本 | 选择理由 |
|---|---|---|
| JDK | JDK 8(如1.8.0_202) | 生态兼容性最好,Tomcat 8.5和大量老教程都围绕它展开 |
| Tomcat | 8.5.x 或 9.0.x | 支持Servlet 3.1/4.0,使用javax包名,与课设教材一致 |
| MySQL | 5.7.x | 稳定且教程多,安装不折腾;如果选8.0需要额外处理时区和驱动类名 |
| IDE | IntelliJ IDEA,Community版够用 | 现在学生几乎默认IDEA,Eclipse老项目也能导入运行 |
不推荐一上来用JDK 11或17,不是不能用,而是Tomcat版本、JSTL标签库、旧版IDE插件都容易出现适配问题,遇到报错排查起来很花时间,纯属给课设增加难度。Tomcat 10.1之后有一个致命变化:包名从javax.servlet改为jakarta.servlet,老代码直接编译报错。如果你手里这份源码是传统写法,贸然配一个Tomcat 10,第一眼看到的就是满屏的"cannot find symbol",所以版本必须核对清楚。
MySQL方面,5.7和8.0都要会用,但驱动类名不一样:5.7是com.mysql.jdbc.Driver,8.0是com.mysql.cj.jdbc.Driver。如果你的数据库是MySQL 8.0,连接串里还要加serverTimezone=Asia/Shanghai,否则启动时会报"The server time zone value"错误。
2.2 IDEA新建JSP项目的两种姿势
拿到项目源码之后,第一步是在IDEA里把它正确加载成可运行的项目。非Maven的老式JSP项目,直接在IDEA里选择Open,找到源码目录打开即可。IDEA会识别出web目录和WEB-INF/web.xml。如果IDEA没有自动识别Web模块,可以进Project Structure里的Facets,手动添加Web,并在Web Resource Directory里指定前端页面目录。这一步很关键,配置错了后面Tomcat运行时会直接报"Artifact 'xxx' hasn't been deployed"。
从零新建JSP项目,我建议用Maven方式。New Project里选Maven,勾选Create from archetype,选择maven-archetype-webapp,然后把groupId和artifactId填好。生成的目录结构是src/main/java、src/main/resources、src/main/webapp,JSP页面放在webapp下,Java源码放在main/java下。pom.xml里需要补两个依赖,一个是Servlet API,注意scope设为provided,因为Tomcat自带了Servlet容器实现;另一个是MySQL驱动,按数据库版本选择5.x或8.x。
非Maven的老式结构也很常见,Java源码在src下,页面在web目录下,MySQL驱动jar直接放到web/WEB-INF/lib里,IDEA里右键Add as Library。两种方式都可以跑,Maven的好处是依赖管理清晰,war打包一条命令搞定;非Maven的好处是贴近很多网课教程,出了问题好搜答案。
2.3 连接数据库的必要准备
环境层面最后一步是数据库。在Navicat或IDEA Database面板里新建一个数据库,名字和项目里DBUtil配置保持一致,比如mingcha,字符集选utf8mb4。然后直接运行项目里自带的mingcha.sql脚本,把表和初始化数据灌进去。如果手边没有可视化工具,命令行也可以:mysql -u root -p回车后输入密码,再执行source D:/mingcha.sql,注意路径里的斜杠用正斜杠。
数据库连不上是JSP项目最常见的报错来源之一。检查顺序是:MySQL服务有没有启动、root密码对不对、数据库名是否一致、驱动jar是否在WEB-INF/lib下面、URL里的端口是不是默认3306。这五个检查点一次过完,90%的Connection refused问题都能解决。
3. 数据库设计与核心表结构
3.1 从"茗茶"主题反推数据模型
"茗茶文化网站"的数据库设计核心是围绕"商品"和"会员"两条线展开。我习惯把表拆成六张主表和一张明细表:管理员表负责后台入口,会员表存前台用户,分类表定义茶叶所属类别,茶叶表存商品详情,订单主表记录一次购买行为,订单明细表记录该订单包含哪些茶叶、数量各是多少。再加一张留言表存用户反馈,整个业务闭环就有了。
表之间的关联关系很简单:分类和茶叶是一对多,一个分类下面有多款茶叶;会员和订单是一对多,一个用户可以下多笔订单;订单和订单明细是一对多,一笔订单包含多个商品条目。这几条关系线,正好是MySQL外键和JOIN查询的练习重点。答辩老师特别喜欢在这里追问:如果删除一个分类,下面还有茶叶怎么办?答案就是外键约束会让删除失败,这正是外键存在的意义——保护数据完整性。
3.2 建表SQL与字段设计细节
核心的茶叶表和分类表建表语句大致这样:
CREATE TABLE t_category ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '分类ID', name VARCHAR(50) NOT NULL UNIQUE COMMENT '分类名称', remark VARCHAR(255) COMMENT '分类描述' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='茶叶分类表'; CREATE TABLE t_tea ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '茶叶ID', category_id INT NOT NULL COMMENT '所属分类', name VARCHAR(100) NOT NULL COMMENT '茶叶名称', intro TEXT COMMENT '茶叶简介/文化典故', price DECIMAL(10,2) NOT NULL COMMENT '单价', stock INT NOT NULL DEFAULT 0 COMMENT '库存', image VARCHAR(255) COMMENT '图片路径', is_recommend TINYINT NOT NULL DEFAULT 0 COMMENT '首页推荐 0否 1是', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '录入时间', CONSTRAINT fk_tea_category FOREIGN KEY (category_id) REFERENCES t_category(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='茶叶信息表';字段设计有几个细节值得说。价格字段必须用DECIMAL(10,2),不要用float或double,否则做金额累加时会出现0.1+0.2不等于0.3的经典精度问题。intro字段用TEXT,因为"文化网站"需要在详情页放产地故事、品鉴描述,如果字段是VARCHAR(255)根本放不下。is_recommend用TINYINT(1)当布尔值用,配合首页推荐位的逻辑,0不推荐、1推荐,这种设计在很多老师眼里是有"设计感"的。create_time用DATETIME DEFAULT CURRENT_TIMESTAMP,连插入时间都不用写Java代码,省事且规范。
订单主表和明细表再补充两句。订单表里order_no字段我一般设计成VARCHAR(32)并加UNIQUE约束,订单号由程序生成,避免并发下单时主键冲突。订单明细表里会冗余一个tea_name字段,因为商品名称之后可能被管理员改名,但订单历史里的商品名要保持下单时的样子,这叫历史数据快照。把这个点写进设计说明文档,是很容易让答辩老师眼睛一亮的小细节。
3.3 数据初始化与导入方式
造数据也是门学问。茶叶名称尽量用真实品种,比如西湖龙井、碧螺春、武夷大红袍、金骏眉、白毫银针、六安瓜片,分类也老老实实分成绿茶、红茶、乌龙茶、白茶、黑茶、花茶。每类放两到三条数据,首页推荐位标一部分,图片路径填好。这样前台页面一打开就有内容可看,演示效果比空的列表强太多。
导入时建议把整个创建表和插入数据的SQL放在一个sql文件里,末尾加几句SELECT验证语句。如果导入后某张表行数为0,优先查是不是SQL脚本里有语法错误,或者建表顺序不对导致外键报错。分类表必须先于茶叶表创建,因为茶叶表的外键依赖分类表的主键,这个顺序搞反了,导入会直接失败。
4. 核心功能实现与代码解析
4.1 经典三层结构:JSP只做展示
拿到源码后先看包结构,规范的JSP项目一定是分层清晰的:entity包放实体类,每个表对应一个POJO;dao包放数据访问类,专门写SQL;servlet包放控制层,接收浏览器请求、调用DAO、选择跳转页面;util包放工具类,比如数据库连接管理、字符串校验。页面放在webapp下,按功能分子目录:根目录放首页和茶叶展示,admin目录放后台管理页面。
设计原则是JSP页面里尽量不要写大段Java逻辑。首页用JSP指令引入DAO,再用for循环输出列表可以,但涉及复杂运算、事务处理、权限判断这类逻辑,一定放Servlet和Service层。我做项目时习惯先把DAO写扎实,因为数据访问是JSP项目最核心也是答辩最常盘问的部分。
4.2 DBUtil与JDBC连接:别小看这个工具类
几乎每个JSP项目都有一个DBUtil,代码看起来就十几行,但坑全藏在细节里。我惯用的版本是:
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/mingcha?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs != null) try { rs.close(); } catch (SQLException ignored) {} if (ps != null) try { ps.close(); } catch (SQLException ignored) {} if (conn != null) try { conn.close(); } catch (SQLException ignored) {} } }连接串里的characterEncoding=utf8要保留,这是JDBC层面的中文乱码防护,配合页面的charset设置,基本可以告别问号乱码。驱动类名按数据库版本二选一,别混用。关闭资源的顺序必须是ResultSet、PreparedStatement、Connection,反了会导致结果集还没读完连接就被关掉。
DAO里执行查询时,坚持用PreparedStatement而不是Statement,好处不只是防SQL注入,更重要的是参数化拼接能让代码更清晰。比如"按照分类查茶叶并分页"这个场景,语句是SELECT * FROM t_tea WHERE category_id = ? LIMIT ?, ?,三个问号对应分类ID、起始行数、每页大小,代码可读性比字符串拼接高一个档次,答"为什么不用Statement"时也有了标准答案。
4.3 登录注册与Session会话控制
登录逻辑是JSP课设的必问考点。核心Servlet的骨架是:
@WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); if (username == null || password == null || username.trim().isEmpty() || password.isEmpty()) { request.setAttribute("error", "用户名和密码不能为空"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } Member member = new MemberDao().findByUsernameAndPwd(username, password); if (member != null) { HttpSession session = request.getSession(); session.setAttribute("loginUser", member); response.sendRedirect("index.jsp"); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } } }这里有个非常容易踩的坑:request.setCharacterEncoding("UTF-8")必须写在获取任何参数之前,否则POST请求里的中文参数会乱码。登录成功用sendRedirect而不是forward,是为了防止用户刷新页面时重复提交表单——转发后地址栏不变,刷新就会再次POST;重定向之后地址栏是目标页面,刷新也不会重复登录。
Session的作用不仅在登录状态,购物车也很适合放Session里。用一个Map<Integer, Integer>存茶叶ID和数量,结账时遍历这个Map插入订单和订单明细,最后清空Map。这种方式对课设来说是最务实的,因为数据库购物车需要处理游客购物、登录后合并购物车等逻辑,工作量直接翻倍。
用户中心的"个人信息展示页面"就是从Session里取出loginUser对象,再根据用户ID去查订单列表,在页面上用表格展示订单号、总金额、下单时间、订单状态。顺带可以把注册时间显示出来,从Session里拿write时间字段即可。
4.4 茶叶列表、搜索与分页
首页推荐位是最加分也最省事的模块,一条SQL就能搞定:
SELECT * FROM t_tea WHERE is_recommend = 1 ORDER BY id DESC LIMIT 8分类列表页通常要支持点击左侧分类切换右侧商品,并且加分页。分页逻辑的核心是三个变量:总记录数、每页条数、当前页码。每页条数固定为8,当前页码从request.getParameter("page")里解析,取不到默认是1。总页数用(totalCount + pageSize - 1) / pageSize计算,这种向上取整的写法比直接除法稳妥,因为总能保证最后一页有页可显示。分页条生成时要注意:第一页时"上一页"不能点,最后一页时"下一页"不能点,当前页高亮显示。
搜索功能用模糊查询:
String sql = "SELECT * FROM t_tea WHERE name LIKE ? OR intro LIKE ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "%" + keyword + "%"); ps.setString(2, "%" + keyword + "%");搜索框提交的关键词一定要通过参数绑定传进去。有些人图省事直接拼SQL:
SELECT * FROM t_tea WHERE name LIKE '%" + keyword + "%'这个写法一旦keyword里包含单引号,SQL就会断句错乱,甚至被注入恶意语句。平时自己演示没感觉,答辩现场被老师点出来就非常尴尬。
茶叶详情页的逻辑最简单,t_detail.jsp?id=xxx,通过TeaDao.findById查出数据,页面展示大图、名称、价格、库存、简介,再放一个"加入购物车"按钮,以及一段产地介绍和品鉴文字,把"文化网站"的味道做出来。加购成功以后用前端弹窗提示,并跳回当前页面继续浏览,体验比直接跳到购物车更自然。
4.5 后台管理的Filter拦截
后台页面不能裸奔,必须做登录拦截,标准做法是Filter。但Filter的路径配置有一个经典坑:如果过滤器拦截/admin/*,那么/admin/login.jsp和后台登录Servlet本身也会被拦截,进而形成"还没登录就跳去登录页,结果登录页也被拦截"的重定向死循环。解决办法是在过滤器里对登录相关的URI放行:
@WebFilter("/admin/*") public class AdminFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String uri = request.getRequestURI(); if (uri.endsWith("login.jsp") || uri.endsWith("/login")) { chain.doFilter(req, resp); return; } Object admin = request.getSession().getAttribute("admin"); if (admin == null) { response.sendRedirect(request.getContextPath() + "/admin/login.jsp"); } else { chain.doFilter(req, resp); } } }后台增删改的套路也很固定:管理页面用表格列出数据,每行带"编辑"和"删除"链接;删除前用JavaScript弹确认框,防止误操作;编辑页面从地址栏的参数id查出数据回显到表单,提交之后执行UPDATE再重定向回列表页。注意修改和删除操作完成后,也要用sendRedirect而不是forward,理由还是防止刷新重复提交。
后台图片上传是个容易卡壳的环节。form标签要加enctype="multipart/form-data",Servlet里用request.getPart("image")获取文件,文件名用时间戳重命名,避免中文和重复覆盖,然后写入磁盘:
Part part = request.getPart("image"); String fileName = System.currentTimeMillis() + ".jpg"; String uploadPath = getServletContext().getRealPath("/upload"); part.write(uploadPath + File.separator + fileName);数据库里只存"upload/文件名"这个路径,页面上 的src只用添加上下文路径就能访问。记得项目部署目录下的upload文件夹要先创建好,否则part.write会直接报目录不存在的IoException。
5. 调试部署全流程与常见问题排查
5.1 本地跑通的验证清单
从拿到源码到页面打开,我习惯按清单逐项确认,这样出了问题能快速定位到具体环节:
- MySQL服务已启动,命令行能正常连库。
- 数据库已导入,表数量符合预期。
- DBUtil里的用户名、密码、库名与本地一致。
- 驱动jar已经放对位置:非Maven项目放WEB-INF/lib,Maven项目pom依赖已添加。
- IDEA里Tomcat运行配置已添加,Deployment里已挂载Artifact。
- 点击运行后观察日志,出现"Server startup in xxx ms"表示容器启动成功。
- 浏览器访问http://localhost:8080/项目名/,首页能正常打开。
最后一步最容易出错,很多新手访问的时候忘了加"项目名"这个上下文路径,直接访问8080端口,看到的自然是Tomcat默认首页而不是项目首页。添加Deployment时Artifact的名称,就是访问路径中的上下文名,资源包文件名建议改成简短的英文名,比如mingcha。
5.2 打包部署到Tomcat的完整步骤
课设交付经常要求能部署到另一台机器上演示,所以打包部署这个环节必须会。非Maven项目的操作是:File -> Project Structure -> Artifacts,点加号选Web Application: Archive,命名为mingcha。然后Build -> Build Artifacts -> Rebuild,target目录下就会生成mingcha.war。把这个war文件复制到Tomcat的webapps目录下,启动Tomcat,war会被自动解压成同名目录,访问http://ip:8080/mingcha/即可。
Maven项目更简单,在项目根目录执行mvn clean package,target目录下直接产出war。如果执行过程中提示测试失败,可以先mvn clean package -DskipTests跳过测试,部署这种课设项目一般不需要跑单元测试。
改了Tomcat默认端口的方法也要掌握:编辑conf/server.xml,把 改成其他端口,保存后重启。注意关闭Tomcat用bin目录下的shutdown.bat,不要直接kill进程,否则端口会延迟释放,重启时容易报端口占用。
5.3 高频报错速查表与解决思路
我把这些年调JSP项目遇到的高频问题整理成了一张速查表,直接对着排查效率最高:
| 报错现象 | 可能原因 | 解决思路 |
|---|---|---|
| Tomcat启动闪退 | 8080端口被占用 | netstat -ano查看占用进程,改server.xml端口或结束占用进程 |
| 页面404 | 项目未部署成功或访问路径不对 | 确认Deployment里Artifact名称,访问路径加项目名 |
| 500错误,Unable to compile class for JSP | JSP编译临时目录权限或jar冲突 | 删掉Tomcat的work目录重启;排查lib里是否重复放了servlet-api |
| ClassNotFoundException: com.mysql.jdbc.Driver | 驱动jar缺失或版本不对 | 驱动jar放WEB-INF/lib;MySQL8改用com.mysql.cj.jdbc.Driver |
| Communications link failure | 数据库没启动或URL端口错误 | 启动MySQL,核对URL里的IP和端口 |
| The server time zone value错误 | MySQL8时区问题 | URL加serverTimezone=Asia/Shanghai |
| 中文乱码 | 页面/Servlet/数据库连接三层编码不一致 | 页面charset=UTF-8,Servlet里setCharacterEncoding放在开头,连接串加characterEncoding=utf8 |
| 刷新后表单重复提交 | 操作成功后用了forward | 增删改成功后一律sendRedirect |
| 图片不显示 | 上传目录不对或图片路径少了上下文 | 确认图片写入部署目录的upload文件夹,页面引用路径带上下文前缀 |
| 后台无限跳登录页 | Filter拦截了登录请求 | 在Filter里对login.jsp和登录Servlet放行 |
5.4 遇到报错时的通用排查姿势
遇到任何报错,先养成看日志的习惯。Tomcat的logs目录下,catalina日期.log是主日志,localhost日期.log是当前应用日志。报错堆栈很长时,不要从第一行开始读,直接往下翻找到第一个"Caused by",那才是底层异常发生的根源。比如一堆Servlet异常包着一条MySQLSyntaxErrorException,八成就是SQL语句本身有语法问题,把SQL单独拿到Navicat里执行一遍立刻见分晓。
IDEA里调试也有个小技巧:在DAO的查询方法上打断点,运行到断点时看PreparedStatement里绑定的参数是否正确、是否为空、类型对不对。很多"查不出来数据"的问题,本质是传入的参数是null或者类型不匹配,一眼就能看出来。
6. 从课设到毕设的扩展思路
6.1 技术栈升级的方向
如果这份项目要升级成毕设,不建议推倒重写,而是在原骨架上做增量改造。后端可以从DriverManager升级到数据库连接池,比如DBCP或HikariCP,代码改动不大,但体现的性能意识是答辩加分点。再进一步可以引入MyBatis或MyBatis-Plus做ORM,把DAO里大量重复的JDBC模板代码精简掉。缓存层面,首页推荐的热门茶叶列表可以加Redis缓存,把高频查询从MySQL上扛下来。日志也不要再用System.out.println,换log4j2或slf4j统一日志输出。
如果导师要求技术新一点,可以考虑把后端整体换成Spring Boot。但要注意,Spring Boot对JSP的内嵌支持并不好,需要额外引入jasper依赖,并在application.properties里配置视图前缀和后缀。如果导师没有强制要求,我更推荐保留JSP技术栈,把精力放在数据库优化、功能完整度和工程规范上,这样整套系统依然是一个完整的、能运行的JavaWeb应用,不会两头不讨好。
6.2 功能层面可以加的亮点
在原有基础上加功能,优先加那些"能看见数据流转"的模块。销量统计可视化就很有画面感:后台用ECharts柱状图展示各分类茶叶的销量,数据来自订单明细表按分类聚合,既用上了表关联查询,又让后台页面看起来很有档次。还可以把首页推荐位从写死的is_recommend字段改成按销量排名动态推荐,逻辑更真实,实现也不难。
订单状态流转也可以做成前后台联动:用户下单生成待处理订单,管理员在后台发货后状态改为已发货,用户端个人中心能看到状态变化。这和"个人信息展示页面"天然衔接,整个用户闭环就更完整了。再加一个留言审核功能,用户在前台留言,管理员后台审核通过后展示,这个模块既涉及多表关联,又体现内容安全意识。
安全方面可以做的细节也不少。登录失败次数限制,比如连续失败三次锁定账号十分钟,需要数据库加字段记录失败次数和锁定时间;密码不存明文,加盐后做SHA-256摘要存储,数据库里即使被拖库,也不至于直接暴露用户密码。这些功能改动不大,但写进论文的安全章节非常有效。
最后说点实在的经验。我帮人调过不少JSP课设,发现一个规律:能在短时间内把环境跑通的新手,通常不是JSP语法背得熟,而是会看日志——报错第一反应不是慌乱,而是去Tomcat的logs目录翻catalina日志。这个"茗茶"项目跑起来只是第一步,真正答辩拿高分的底气,来自你能对着DBUtil逐行解释清楚:连接怎么建立、PreparedStatement为什么安全、购物车为什么放在Session里、事务应该处理哪些环节。我还见过有同学在自己电脑上跑得飞起,演示时换成另一台机器,因为数据库没提前初始化直接翻车。所以再提醒一句:交付前一定把"数据库初始化脚本、环境版本、部署步骤"写成说明文档放进项目包,这比任何花哨功能都更能让验收老师放心。