简介:基于 Java+JSP 的洛阳旅游管理系统是一套面向毕业设计场景的完整源码与数据库资源,适合计算机相关专业学生用于课程设计、毕设参考或项目二次开发。系统采用 JSP+jQuery+Servlet+JDBC 的经典技术组合,涵盖前台门户展示与后台管理两端,包含景点、资讯、留言、管理员、系统设置等模块,并支持景点图片上传、名称模糊搜索、轮播图展示及天气信息展示等细节功能。资源包共 349 个文件,以 JSP 页面、Java 类、JS 脚本、CSS 样式为主,辅以 SQL 数据库脚本、图片与字体文件及少量配置文件,整体约 30.18MB,结构清晰,导入 IDEA 并配置 MySQL 5.7、Tomcat 9、JDK 1.8 环境即可运行。目前已有 33 人浏览学习,适合作为传统 Java Web 开发从入门到综合实践的学习样板,可帮助读者快速理解 Servlet+JDBC 的请求处理流程、前后台功能组织方式以及旅游类网站常见业务的实现思路。
1. 基于 java+jsp 的洛阳旅游管理系统:课程设计的“最后一公里”,也是中型 Servlet 项目的缩影
如果你在毕业设计或 Java 课程作业里拿到“基于 java+jsp 的洛阳旅游管理系统(源码+数据库)”,第一反应大概是:又是一套老掉牙的增删改查。但真把这份源码打开,你会发现它不像教材里那种只能跑通一个页面的 demo——它有景点、线路、订单、登录、角色权限,数据库脚本里有上百条表结构和初始数据。这类项目恰恰是检验 Java Web 基本功的试金石:Servlet 生命周期、JSP 标签库、JDBC 连接管理、Session 会话,全都要串起来才能跑得动。适合两类人:一是课程设计想交一份“能演示、能答辩”的完整系统的学生;二是准备 Java 面试前想找一个真实项目练手、能讲清楚每一层在干什么的求职者。这篇文章就按我实际做这类系统的顺序——先看数据库、再理后端分层、然后调权限和会话、最后聊部署排查——把整个落地路径拆给你。
2. 把 145 条表结构读薄:洛阳旅游的实体关系与数据库落库
拿到源码包,第一步不是急着往 IDE 里导,而是先把数据库脚本从头到尾读一遍。这个项目叫“洛阳旅游管理系统”,核心数据逃不出三类:用户、景点、订单。你看到的“145”通常指数据库脚本的版本编号或者表/初始化数据行数,不管哪种,它都说明这套库不是玩具,里面起码有角色数据、旅游线路、景区分类、评论留言这类真实业务表。先读懂表关系,再动手改代码,后面所有坑都会少一半。
2.1 登录与角色:user 表为什么要有 role 字段而不是三张表
很多教材项目会把“管理员”和“普通用户”拆成两张表,甚至三张表。但这套系统更常见的做法是在同一张 user 表里放一个 role 字段,用 int 或者 varchar 区分角色。为什么这样设计?因为洛阳旅游管理系统的权限层级并不复杂——无非是管理员维护景点和订单,普通用户浏览景点、下单、查看自己的订单。两种角色共享几乎相同的字段:用户名、密码、手机号、注册时间。拆成多张表只会让登录查询变成 union,反而增加麻烦。
看表结构时重点盯三个地方。第一,密码字段是不是明文。如果脚本里初始用户的密码是 123456 这种直接可读的字符串,说明项目年份较早或课程设计取向,复制到自己项目里一定要改成 MD5 或 BCrypt 加密存储。第二,role 字段的取值范围,是 0/1 还是 1/2,这决定了后端的权限判断怎么写。第三,唯一索引在哪个字段上,一般应该是 username,否则注册接口容易被重复数据搞崩。这三处看清了,登录模块的改造方向也就定了。
2.2 景点、分类与订单:一对多关系怎么建外键
旅游系统的业务核心是“景点”和“订单”。景点表一般包含景点名称、所在区域(洛阳下面有老城、涧西、洛龙等)、简介、图片路径、门票价格、开放时间这些字段。图片路径这一项要特别留意:JSP 项目里图片有两种存法,一种是上传到服务器磁盘,数据库只存相对路径;另一种是直接把图片以 Base64 或二进制塞进数据库。前者是主流,后者会让数据库体积暴涨而且页面加载慢。如果脚本里用的是 longblob 类型字段存图片,建议你改成存路径,这是这类项目最常见的改造点。
订单表必然通过外键关联用户和景点:order 表里有 user_id 和 scenic_id 两个外键字段。查询的时候用 JOIN 把用户名和景点名拼出来。看懂这张表的时候顺带留意一下订单状态字段——是 int 还是 varchar,取值有哪几种,前端下拉框和后端逻辑是否一致。很多项目翻车就翻在状态字段取值不统一:数据库里 0 表示未支付,代码里却用 1 判断。
2.3 建库建表 SQL:utf8mb4、时间戳与索引
拿到 sql 文件后,先用文本编辑器打开看建库语句。字符集是不是 utf8mb4 很关键:如果是老旧的 utf8,插入“洛阳”“龙门石窟”没问题,但一旦遇到 emoji 或者生僻字就会报 Incorrect string value。更坑的是,如果表结构是 utf8 而连接串里写了 characterEncoding=utf-8,数据库会直接拒绝带特殊字符的写入。改法是把整个 sql 文件里的 CHARSET=utf8 全局替换成 CHARSET=utf8mb4,再把连接串加上 useUnicode=true&characterEncoding=utf-8。
时间字段建议统一用 datetime 而不是 timestamp。timestamp 的范围只能到 2038 年,而且受时区影响。如果用 datetime,Java 侧的 java.util.Date 映射不会有任何意外。索引方面,除了主键,至少给外键字段 user_id、scenic_id 和订单表的 create_time 加上普通索引。课程设计的数据量不大,不加也能跑,但这属于面试时能讲的点——你主动加了索引并且能说出“覆盖高频查询的 where 条件”,比背十遍索引原理更有说服力。
-- 以常见的三张核心表为例,展示洛阳旅游系统的库表设计骨架 CREATE DATABASE IF NOT EXISTS luoyang_tour DEFAULT CHARACTER SET utf8mb4; USE luoyang_tour; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, role INT NOT NULL DEFAULT 0, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_scenic ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, district VARCHAR(50), price DECIMAL(10,2), image_path VARCHAR(255), intro TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, scenic_id INT NOT NULL, order_time DATETIME DEFAULT CURRENT_TIMESTAMP, status INT DEFAULT 0, KEY idx_user (user_id), KEY idx_scenic (scenic_id), CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_order_scenic FOREIGN KEY (scenic_id) REFERENCES t_scenic(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段 SQL 里 KEY idx_user 和 KEY idx_scenic 就是给外键查询建索引,JOIN 的时候不会全表扫。外键约束在课程设计里可以保留,它能让数据库层面保证不产生孤儿订单;但如果你打算把这个项目改造成 Spring Boot + MyBatis 再上线,外键通常会被去掉,改成应用层校验,原因是分布式和高并发下外键会影响插入性能。另外注意 t_user 的 role 字段默认值是 0,意思是一般用户,管理员由脚本里的 INSERT 语句单独插入 role=1 的记录。
导入数据库我用命令行而不是 Navicat 的导入向导,因为命令行能看到完整的报错信息。命令是 mysql -uroot -p < luoyang_tour.sql,如果报错就去看报错行号对应的 SQL。很多时候是 sql 文件里带了 DROP DATABASE 语句,在别人的机器上执行会直接删库,导入前把这类危险语句注释掉——这是血泪经验。
3. 在 Servlet + JSP 里实现增删改查:三层结构、请求流转与最小可运行代码
数据库落好以后,开始碰 Java 代码。这套系统的后端骨架是经典三层:Servlet 接收请求、Service 处理业务、DAO 操作数据库。JSP 只做展示。很多新手把 JDBC 代码直接写在 Servlet 里,一页代码几百行,跑通是能跑通,但答辩或者面试的时候一问“订单状态为什么在两个地方各写了一份判断”就答不上来。分层不是做样子,而是让每一层能独立修改。
3.1 实体类、DAO、Service 的分层边界
打开源码包,先看包结构。常见的命名是 com.luoyang.entity、com.luoyang.dao、com.luoyang.service、com.luoyang.servlet,对应实体、数据访问、业务逻辑、控制器四层。实体类就是数据库表的 Java 映射,字段名和表列名一一对应,用 int 对应 INT,用 BigDecimal 对应 DECIMAL,用 java.util.Date 对应 DATETIME。不要在实体类里写业务方法,它就是单纯的数据容器。
DAO 层只做一件事:写 SQL。查询景点列表、按 id 查订单、插入新用户,这些方法里只有 JDBC 模板代码——获取连接、预编译、执行、解析 ResultSet、关闭资源。Service 层调 DAO 的接口,处理业务规则:比如下单时要判断用户是否登录、景点是否存在、库存(门票余量)够不够。Servlet 层只负责两件事——从 request 里取参数,把结果 setAttribute 到 request 或者 session,然后 forward 或者 redirect 到 JSP。这个边界一旦清晰,你改任何一处都不会牵连到另外两层。
以“景点管理”为例,后端至少要这一组方法:list() 查全部、getById() 查单个、insert() 新增、update() 修改、delete() 按 id 删除。有的项目还会把分页也塞进 DAO,用 LIMIT offset, size 实现。课程设计里不分页也能交差,但面试官如果问“数据量大了你怎么优化”,你能说出分页和索引,就已经超过了大部分应届生。
3.2 景点管理增删改查:一个 Servlet 处理一类资源的写法
最省事的 Servlet 设计是:一个 ScenicServlet,用不同的参数或者请求路径区分动作。常见做法是给 Servlet 配置多个 URL 映射,或者在一个 doGet/doPost 里用 action 参数分派。对于课程设计这个体量,action 参数分派最简单,也最好讲清楚。
@WebServlet("/scenic") public class ScenicServlet extends HttpServlet { private ScenicService scenicService = new ScenicService(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String action = req.getParameter("action"); if ("list".equals(action)) { List<Scenic> list = scenicService.list(); req.setAttribute("scenicList", list); req.getRequestDispatcher("/scenic/list.jsp").forward(req, resp); } else if ("delete".equals(action)) { int id = Integer.parseInt(req.getParameter("id")); scenicService.deleteById(id); resp.sendRedirect(req.getContextPath() + "/scenic?action=list"); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String action = req.getParameter("action"); if ("add".equals(action)) { Scenic scenic = new Scenic(); scenic.setName(req.getParameter("name")); scenic.setDistrict(req.getParameter("district")); scenic.setPrice(new BigDecimal(req.getParameter("price"))); scenic.setIntro(req.getParameter("intro")); scenicService.insert(scenic); resp.sendRedirect(req.getContextPath() + "/scenic?action=list"); } } }代码有两点要说清楚。第一,req.setCharacterEncoding("UTF-8") 必须放在读取任何参数之前,否则 POST 提交的中文直接乱码。第二,新增或删除成功后跳转用的是 sendRedirect 而不是 forward,这是为了避免表单重复提交——用户按 F5 刷新时,如果上次请求是 forward 转发,浏览器会重新提交一次表单,订单或者景点记录就会多出一条。改成重定向之后地址栏变成列表页的 URL,F5 刷新只是重新查列表,不会重复写库。
这个 Servlet 里没有写任何 JDBC 代码,全部委托给 ScenicService。这就是分层的意义——如果你要把数据库连接池从 DriverManager 换成 Druid,只需改 DAO 层,Servlet 和 JSP 一行都不用动。
3.3 JSP 页面怎么拿数据:JSTL 与 EL 的配合
JSP 里最忌讳写大段 Java 代码,也就是 Scriptlet。项目里如果看到 < % ... % > 这种块,尽量换成 JSTL 标签。原因很实际:JSP 页面里混 Java 代码,页面设计师没法改样式,而且 Java 代码在 JSP 里出了异常,报错信息非常难看。EL 表达式负责取值,JSTL 的 c:forEach 负责循环,两者配合就能搞定 90% 的列表页面。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <table border="1"> <tr> <th>景点名称</th><th>所在区域</th><th>门票价格</th><th>操作</th> </tr> <c:forEach items="${scenicList}" var="scenic"> <tr> <td>${scenic.name}</td> <td>${scenic.district}</td> <td><fmt:formatNumber value="${scenic.price}" type="currency"/></td> <td> <a href="${pageContext.request.contextPath}/scenic?action=edit&id=${scenic.id}">编辑</a> <a href="${pageContext.request.contextPath}/scenic?action=delete&id=${scenic.id}" onclick="return confirm('确定删除吗?')">删除</a> </td> </tr> </c:forEach> </table>这段 JSP 有两点值得学习。第一,href 里拼的是 ${pageContext.request.contextPath} 而不是写死的项目名,这样把项目改个名字或者部署到别的容器,链接不会断。第二,删除按钮加了 onclick confirm 确认弹窗,这是一层前端拦截,防止手滑误删。但你要明白,这只是用户体验层面的保护,不是安全层面的——攻击者可以直接构造一个 /scenic?action=delete&id=3 的地址来删除数据,所以后端 servlet 里必须做管理员角色校验,不能只看前端有没有按钮。
格式化价格用了 fmt:formatNumber,这是 JSTL 的格式化标签,避免出现 12.0 这种不好看的显示。如果项目里没引入 JSTL 的 jar 包,页面会报 500 错误,报错信息是“Unable to find tag library”。把 jstl-1.2.jar 和 standard.jar 放进 WEB-INF/lib 就能解决。这也是老 JSP 项目最容易在换个环境后原地炸裂的点。
4. 把登录和会话管起来:过滤器、Session 与角色权限
洛阳旅游管理系统既然有用户和管理员两种角色,登录和权限就是必须能讲清楚的部分。面试问“你怎么实现登录拦截”时,答“每个 Servlet 里都写一遍判断登录”当然不行,会被追问“那新加一个 Servlet 你忘了写怎么办”。正确方案是过滤器,Web 容器在请求到达 Servlet 之前先过一遍 Filter,登录校验在那里统一做掉。
4.1 登录校验的过滤链:LoginFilter 怎么写
过滤器在 web.xml 里配置,或者在 Servlet 3.0+ 用 @WebFilter 注解。它的执行时机是:请求进入容器后,先经过 Filter,再进入 Servlet。所以只要在 Filter 里发现 session 里没有登录用户,就直接重定向到登录页,不给 Servlet 任何执行机会。
@WebFilter("/*") public class LoginFilter implements Filter { @Override 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(); boolean isLoginPage = uri.endsWith("login.jsp") || uri.endsWith("LoginServlet") || uri.contains("/css/") || uri.contains("/js/") || uri.contains("/images/"); if (isLoginPage || (session != null && session.getAttribute("currentUser") != null)) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() + "/login.jsp"); } } }注意 getSession(false) 的 false 参数,意思是“如果当前没有 session 就返回 null,而不是新建一个”。为什么不直接 getSession()?因为如果一个匿名用户访问登录页,你给他新建一个 session 纯属浪费空间,而且会让“session 里有用户”这个判断失效。另外,静态资源 css/js/images 必须放行,否则页面上所有样式和图片全挂——这是新手写过滤器最容易翻车的地方,症状是登录页能打开但白花花一片没有样式。
登录成功后的动作是把用户对象塞进 session:session.setAttribute("currentUser", user)。后续 JSP 里用 ${sessionScope.currentUser.username} 就能显示登录用户名。退出登录则是 session.invalidate() 加上重定向回登录页。invalidate 会把 session 整个销毁,这样攻击者拿到的 jsessionid 就彻底失效了。
4.2 权限控制:普通用户和管理员的入口差别
登录拦截只能解决“是否登录”,解决不了“谁能干什么”。普通用户和管理员看到的操作按钮应该不同:普通用户能下单、查看自己的订单;管理员能删景点、改价格、查所有订单。这一步的实现分两层。前端层面,在 JSP 里判断当前登录用户的 role 属性,决定要不要渲染删除按钮,这样普通用户界面上看不到危险操作。后端层面,在 Servlet 里再校验一次 role,防止有人绕过界面直接构造请求。
// 在后端删除景点的 Servlet 方法里,必须检查角色 HttpSession session = request.getSession(false); User currentUser = (User) session.getAttribute("currentUser"); if (currentUser == null || currentUser.getRole() != 1) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; }这 5 行代码就是权限控制的底线。前端隐藏按钮是体验问题,后端检查才是安全问题。课程设计可能没人攻击你,但面试聊到“越权漏洞”时,你能主动说出“前端隐藏不等于安全,后端每个写操作都要校验角色”,这个回答价值很高。更讲究一点的做法是把这类权限判断抽到一个工具类或者再写一个 AdminCheckFilter,避免每个 Servlet 里重复粘贴这段代码。
4.3 会话控制细节:Session 超时与并发登录
Tomcat 里 session 默认超时时间是 30 分钟,web.xml 里可以改。课程设计一般不用动,但你要知道在哪里配置:browser session 超时用 下的 ,单位是分钟。还有一个坑:如果项目里存在两个 Tomcat 实例共用同一个应用,但没配会话保持,用户登录后下一次请求打到另一个实例上,session 就丢了,表现为“刚登录就跳回登录页”。课程设计用单机部署不会遇到,但这是分布式会话的引子——面试时能接住这个话题就行。
另一个常见问题是点击浏览器后退按钮回到登录前的页面。这不一定是漏洞,因为 JSP 是服务器渲染的,后退显示的可能是浏览器缓存的静态页面。要严格控制的话,在敏感页面加 no-cache 响应头。课程设计里不做也行,答辩时如果被问到,说明你已经想过这个问题,答“通过过滤器对未登录请求做重定向,避免访问受保护资源”就足够。
5. 踩坑记录与排查手册:部署、乱码、Tomcat 路径和数据库连接
这一章把我在 JSP 项目上见过最多的四类问题写成排查手册。每条按“现象 → 原因 → 解决”的顺序,你照着对照就行。这些问题每一个我都踩过,写出来免得你再走一遍。
5.1 部署到 Tomcat 后访问 404:Context Path 对不上
现象:代码在 IDE 里按 Ctrl+F11 能跑,浏览器也能打开登录页;但把 war 包丢到 Tomcat 的 webapps 下,输入 http://localhost:8080/项目名 却 404。
原因:IDE 运行时的访问路径是 IDE 配置的上下文路径,可能叫 /luoyang_tour;而 war 包解压出来的目录名是另一个名字,比如 luoyang_tour_145。浏览器地址栏里访问的路径和后端 redirect 的路径对不上,自然找不到资源。还有一种是 JSP 页面里写死了 /luoyang_tour/login.jsp,换到别的部署名就全断。
解决:一是部署前统一项目名,war 包名字改成你要的上下文路径。二是把 JSP 里的所有写死路径替换成 ${pageContext.request.contextPath} 拼接。三是在 Tomcat 的 conf/server.xml 里给 Host 配 Context 的 path 属性——但不建议这样做,因为把路径写死在 server.xml 里,迁移到别的服务器又得改。
5.2 中文乱码:JSP 页面、请求参数和数据库三层查
现象:登录页输入“张三”,提交后页面上显示“å¼ ä¸‰”。数据库里查出来也是乱码,或者插入直接报错。
原因:三层有一个地方字符集不一致就会乱。第一层 JSP 页面本身没指定编码,或者编码是 ISO-8859-1;第二层 Servlet 读取请求参数时没设置 UTF-8;第三层数据库表是 utf8 或 latin1。三者只要有一个掉链子,中文就废了。
解决:JSP 第一行写 <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>。Servlet 里在读取任何参数之前调用 request.setCharacterEncoding("UTF-8"),POST 请求这个设置才有效;GET 请求的参数在 URL 上,Tomcat 8 以上版本默认 UTF-8 还好,老版本需要在 server.xml 的 Connector 上加 URIEncoding="UTF-8"。数据库层面,建表时指定 utf8mb4,JDBC 连接串加 useUnicode=true&characterEncoding=utf-8。你不想每次都在代码里手动 setCharacterEncoding,可以写一个 EncodingFilter 统一设置,效果一样。
5.3 启动时报数据库连接失败:驱动、连接串和密码三件套
现象:Tomcat 启动时控制台刷出一堆 SQLException:Access denied for user 'root'@'localhost',或者 ClassNotFoundException: com.mysql.jdbc.Driver。
原因:前者是用户名密码不对。很多课程设计项目为了保证能跑通,会在代码里写死 root/123456,但你的 MySQL 密码不是这个。后者是缺驱动 jar 包——JSP 项目要手动把 mysql-connector-java 的 jar 放进 WEB-INF/lib,Maven 项目则要在 pom.xml 里加依赖,如果 jar 没打包进 war,部署到别的机器上就报这个错。
解决:先确认 MySQL 密码,然后全局搜 jdbc:mysql:// 找连接串改密码。驱动 jar 版本要跟 MySQL 版本匹配:MySQL 5.x 用 5.1.49,MySQL 8.x 用 8.0.33,连接串写法也略有差异——8.x 需要加 serverTimezone=Asia/Shanghai,否则会报 CST 时区错误。这个时区错误很多人第一次遇到会懵,其实是 MySQL 8 默认时区要求更严格了,加上参数就行。
5.4 自带的管理员账号为什么登不进去
现象:数据库脚本里明明白白插了一条 username=admin、password=123456 的记录,但登录页面一直提示“用户名或密码错误”。
原因:源码里的登录逻辑对密码做了 MD5 加密后再比对,而那条初始数据的密码是明文,数据库里存的是 123456,代码里计算出的 MD5 值是一长串十六进制,永远对不上。
解决:这一步有几个方向。如果你只是演示,把登录逻辑里的加密去掉改成明文比对,是最快的做法,但答辩时容易被问“密码怎么能明文存”。正规做法是写一个小工具类,把初始密码改成加密后的值再 UPDATE 进数据库;或者在注册功能里写一段逻辑,用相同的加密方式存储密码。顺带说一句,我见过最离谱的情况是程序里用了 MD5 加盐,但脚本里的数据是用另一种算法生成的,对不上非常正常。遇到这类问题,先在代码里找密码工具类,再反推初始数据是怎么生成的,不要上来就改加密算法。
5.5 页面样式全丢:项目路径里多了个“工程名”
现象:登录页能打开,但所有 CSS、JS、图片都加载不出来。浏览器 F12 打开开发者工具,Network 标签里一堆 404,请求地址全是 http://localhost:8080/css/style.css,而项目实际访问地址是 http://localhost:8080/luoyang_tour/login.jsp。
原因:JSP 页面里引用静态资源的路径写成了 /css/style.css,少了项目上下文路径。浏览器解析的时候以为 css 文件在根路径下,但应用实际挂在 /luoyang_tour 下,所以 404。
解决:页面里所有静态资源引用改成 ${pageContext.request.contextPath}/css/style.css。如果项目用了 Bootstrap 这类第三方组件,检查引入的路径也是同样的改法。还有一种解法是在 JSP 页面顶部用 <c:set var="ctx" value="${pageContext.request.contextPath}" />,然后资源路径都写成 ${ctx}/...,能少写很多字符。
6. 用这套 145 的库接着做:加一个“评论”需求要动哪几个文件
如果你已经跑通原项目,下一步最值得做的改造是加一个“景点评论”功能。为什么推荐这个?因为评论必然涉及“新增”和“列表查询”,同时关联了用户表、景点表两张表,还牵扯到登录权限——一次改造就把前面所有知识点练了一遍。
具体要动五个文件。第一,数据库加一张 t_comment 表,字段是 id、scenic_id、user_id、content、create_time,外键关联前两表。第二,新建 Comment 实体类和 CommentDao,DAO 里写一个按 scenic_id 查评论列表的方法、一个插入评论的方法。第三,新建 CommentServlet,doPost 接收评论内容,doGet 查询评论列表。第四,在景点详情 JSP 页面加评论区:上面是评论列表,用 c:forEach 遍历;下面是输入框和提交按钮。第五,在 LoginFilter 里确认评论提交的 URL 需要登录才能访问——普通浏览可以看评论,但要发表评论必须登录。整个改造半天足够,但覆盖了增删改查、多表 JOIN、过滤器权限三个核心技能点。
改的时候留意一件事:评论列表查询要 JOIN 查出用户名,不能只显示 user_id。SQL 大概是 SELECT c.*, u.username FROM t_comment c JOIN t_user u ON c.user_id = u.id WHERE c.scenic_id = ? ORDER BY c.create_time DESC。这种 SQL 写一遍,你对外键和 JOIN 的理解就扎实了,面试手写 SQL 也更有底气。
如果你还想往上走,可以把这个项目的登录模块改造成一个简易的 Token 方案:登录成功后生成一个 UUID 存入数据库并返回前端,后续请求带上这个 token,后端校验。这样就从 Session 会话模式自然过渡到前后端分离的认证思路,跟现在主流 Spring Boot + JWT 的面试题也能衔接上。改完这一轮,这套课程设计源码就不再是应付交差的“作业”,而是能写进简历的完整项目了。
我从第一次做这种项目到现在,一直保留一个习惯:每改完一个功能,先把数据库导出一份备份 sql 存到项目外的目录,再启动 Tomcat 验证。这套系统改动频繁,数据库一旦改坏,有备份就有后悔药。希望帮到你。
本文还有配套的精品资源,点击获取