尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于JSP的房屋出租管理系统实战:从设计到源码打包部署

基于JSP的房屋出租管理系统实战:从设计到源码打包部署 简介一套基于JSP的房屋出租管理系统源码包主要面向Java Web初学者、计算机专业学生以及需要快速搭建管理类项目的开发者。系统按照房屋出租实际业务流程设计完整覆盖房源信息维护、租户档案管理、租赁合同签署、租金定期收取和统计报表生成等功能模块可供课程设计、毕业设计或企业内训参考。压缩包共包含771个文件整体大小约11.49MB。文件以HTML页面、CSS样式、JavaScript交互脚本、PNG图片等前端资源为主同时包括JSP动态页面、Servlet控制器、JavaBean业务类、Dao数据访问类以及数据库SQL脚本和项目配置文件。静态资源与后台代码分离目录结构清晰便于导入开发工具后直接运行和二次修改。目前已有101人学习浏览适合以“阅读源码→运行调试→改造扩展”的方式使用。通过对管理员操作、房源展示、收藏处理等流程的跟踪可以直观理解JSP与Java后台的协作机制、数据库访问细节以及MVC分层思想同时对房屋出租领域的数据建模与权限控制也会有更具体的认识。1. 为什么到了现在我依然推荐用 JSP 做房屋出租管理系统如果你是做毕设选题、JavaWeb 课设或者想快速搭一套能演示、能交差的业务系统那么“基于 JSP 的房屋出租管理系统”这个题目几乎是一条性价比最高的路。它不涉及微服务、不碰分布式就是标准的 JavaWeb 三层结构JSP 负责页面展示Servlet 处理请求JDBC 操作数据库。你只要把这几层跑通了一个完整的管理系统就立住了。这套技术栈在 2025 年看起来可能有点“复古”但它足够简单、足够直白特别适合用几十个小时去理解一个 Web 项目从 0 到 1 的完整生命周期。源码打包是这个项目的另一大刚需。很多同学卡在“代码写完了但老师要的是一个能部署、能演示的项目包”。这意味着你要把 IDEA/Eclipse 工程、数据库脚本、部署文档、甚至 Tomcat 配置一起整理成一个压缩包。里面任何一个环节缺了项目在别人的电脑上就跑不起来。这篇文章我会用一篇完整的实战笔记带你从技术选型、数据库设计、核心模块实现一路走到源码打包、部署避坑和验收自查。全是可复现的操作读完你不仅能交差还能讲清楚每个模块为什么这么做。2. 技术选型与项目骨架先认清 JSP 项目的真实边界2.1 为什么不用 Spring BootJSP 在毕设里反而更稳很多同学一上来就问现在不都流行 Spring Boot 吗为什么还要用 JSP这个问题的答案恰恰是这个题目的核心价值。Spring Boot 集成 JSP 有先天缺陷官方不建议生产环境用 JSP默认不支持打包成可执行的 JAR必须用 WAR 包部署到外部 Tomcat而且 JSP 文件要放在src/main/webapp下路径稍有偏差就 404。这些坑我后面会详细讲。对于毕业设计来说你需要的不是“流行”而是“可控”——你能够清晰地解释每一行代码是干什么的能够在答辩时应对老师的追问甚至能够现场改一个 Bug。纯 JSP Servlet JDBC 的项目没有框架的魔法没有自动装配的黑匣子每一个环节都裸露在你面前。我一般会给出这样的选型建议如果你是做课设、毕设明确要求 JSP那就用经典的 JSP Servlet JSP 标准架构数据库用 MySQL 5.7 或 8.0服务器用 Tomcat 8.5 或 9.0。如果你是想学新技术那就直接学 Spring Boot别用 JSP 硬凑。这个标题既然锁定了 JSP我们就把它做深做透。2.2 标准三层结构JSP、Servlet、DAO 各自管什么整个系统我习惯拆成四层每一层的职责必须单一否则后面排查问题会痛不欲生。第一层是 JSP 页面层只负责展示数据和收集用户输入不在页面里写 Java 业务逻辑最多做c:forEach遍历列表。第二层是 Servlet 控制器层负责接收请求、调用 Service、转发或重定向到指定 JSP。第三层是 Service 业务逻辑层校验参数、处理业务规则比如下架房源前检查是否有未结束的合同。第四层是 DAO 数据访问层只做 JDBC 的增删改查返回List实体类或单个对象不掺业务判断。以用户注册为例流程是这样的register.jsp表单提交到UserServletServlet 先做基础非空校验然后调用UserService.register(username, password)Service 里检查用户名是否重复最后调用UserDao.insert(user)把密码加密至少 MD5 加盐后写入数据库。注册成功后重定向到login.jsp?msgsuccess防止表单重复提交。这个链路每一步都可以单独测试出问题能立刻定位到层。2.3 开发工具与工程目录照着搭就不会乱工具选型直接影响你后面打包的效率我提供一个常用的组合主流的 Eclipse 或 IntelliJ IDEA 都支持。JDK 用 1.8 或 11Tomcat 用 8.5/9.0MySQL 用 5.7/8.0IDEA 建议 Community 版就够不用破解旗舰版。工程结构要严格遵循 Maven 的标准目录布局不要手动建 Web 目录否则导出 WAR 的时候资源会丢。house-rental/ ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com/example/houserent │ │ │ ├── controller/ # Servlet 控制器层 │ │ │ ├── service/ # 业务逻辑层 │ │ │ ├── dao/ # 数据访问层 │ │ │ ├── entity/ # 实体类House/User/Contract │ │ │ ├── util/ # JDBC 工具类、MD5 工具类 │ │ │ └── filter/ # 编码过滤器、登录过滤器 │ │ ├── resources │ │ │ ├── db.properties # 数据库连接配置 │ │ │ └── jdbc.properties │ │ └── webapp # Web 根目录 │ │ ├── WEB-INF │ │ │ ├── web.xml # Servlet 映射、过滤器声明 │ │ │ └── jsp/ # 受保护的 JSP 页面 │ │ ├── static/ # CSS/JS/图片 │ │ └── index.jsp │ └── test └── sql └── house_rental.sql # 建库建表脚本 初始数据注意WEB-INF目录下的 JSP 页面无法通过 URL 直接访问必须经由 Servlet 转发跳转这样做的好处是用户不能绕过登录验证直接打开管理页面。比如admin/house_list.jsp放在WEB-INF里访问地址是/house/list由HouseServlet先检查 Session再转发到WEB-INF/jsp/admin/house_list.jsp。3. 数据库设计房屋出租管理系统的六张核心表3.1 从业务需求倒推数据表房屋出租管理系统核心是“房”和“租”业务上至少要覆盖房源管理、租客信息管理、合同管理、租金账单管理、用户管理员管理。我在这里给出一个经过多次迭代的建表方案你在跑通源码之后可以按需增减字段但骨架字段最好保持一致不要一开始就拍脑袋加列。六张表分别是admin_user管理员表存登录账号、密码、姓名、角色超级管理员/普通管理员。house房源表存房屋编号、地址、户型几室几厅、面积、月租金、押金、状态待出租/已出租/已下架、描述、图片路径、录入时间。tenant租客表存姓名、手机号、身份证号脱敏存、紧急联系人、备注。contract合同表存所属房源 ID、租客 ID、起止日期、月租金冗余字段防止房源改价影响历史合同、押金、付款方式、状态生效/已到期/已退租、创建时间。payment_record账单表存合同 ID、费用类型租金/押金/其他、金额、应收日期、实收日期、状态已收/未收/逾期。operation_log操作日志表存操作人 ID、操作内容、操作时间、IP 地址用于审计。3.2 建表 SQL 的关键参数说明下面是简化后的核心建表 SQL我需要你在本地 5.7 以上版本执行时特别注意字符集和自增列这两个点这也是毕设项目里最常见的扣分项。CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rental; DROP TABLE IF EXISTS payment_record; DROP TABLE IF EXISTS contract; DROP TABLE IF EXISTS house; DROP TABLE IF EXISTS tenant; DROP TABLE IF EXISTS admin_user; CREATE TABLE admin_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5加盐后的密码, real_name VARCHAR(50) DEFAULT NULL COMMENT 姓名, role TINYINT DEFAULT 1 COMMENT 角色: 1超级管理员, 2普通管理员 ) ENGINEInnoDB AUTO_INCREMENT1 COMMENT管理员表; CREATE TABLE tenant ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 租客姓名, phone VARCHAR(20) NOT NULL UNIQUE COMMENT 手机号, id_card VARCHAR(18) DEFAULT NULL COMMENT 身份证号, emergency_contact VARCHAR(50) DEFAULT NULL COMMENT 紧急联系人, emergency_phone VARCHAR(20) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT租客表; CREATE TABLE house ( id INT PRIMARY KEY AUTO_INCREMENT, house_no VARCHAR(30) NOT NULL UNIQUE COMMENT 房屋编号, address VARCHAR(200) NOT NULL COMMENT 详细地址, layout VARCHAR(20) DEFAULT NULL COMMENT 户型如两室一厅, area DECIMAL(8,2) DEFAULT NULL COMMENT 面积平方米, monthly_rent DECIMAL(10,2) NOT NULL COMMENT 月租金, deposit DECIMAL(10,2) DEFAULT NULL COMMENT 押金, status TINYINT DEFAULT 0 COMMENT 0待出租 1已出租 2已下架, description TEXT, image_path VARCHAR(255) DEFAULT NULL COMMENT 封面图存相对路径, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT房源表; CREATE TABLE contract ( id INT PRIMARY KEY AUTO_INCREMENT, house_id INT NOT NULL, tenant_id INT NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, monthly_rent DECIMAL(10,2) NOT NULL COMMENT 冗余租金合同期内不变, deposit DECIMAL(10,2) NOT NULL, payment_type TINYINT DEFAULT 1 COMMENT 1押一付三 2押一付一 3半年付 4年付, status TINYINT DEFAULT 1 COMMENT 1生效中 2已到期 3已退租, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_contract_house FOREIGN KEY (house_id) REFERENCES house(id), CONSTRAINT fk_contract_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) ) ENGINEInnoDB COMMENT租赁合同表;逻辑说明house表的monthly_rent是当前挂牌价contract表里的monthly_rent是签约时锁定的租金两者分离开否则你改了房源租金历史合同的账单金额也会跟着变。status字段用TINYINT不要用VARCHAR存“已出租”这种中文排序和检索都很别扭。id_card这种敏感字段在页面展示时要脱敏比如只显示前四位和末四位。参数说明AUTO_INCREMENT是用来控制自增起始值的如果之前删过数据想重置可以ALTER TABLE house AUTO_INCREMENT 1;。ENGINEInnoDB必须显式指定MySQL 5.5 之后默认就是 InnoDB但你交作业时写上这行能体现你理解外键约束和事务。字符集用utf8mb4而不是utf8因为utf8在 MySQL 里存不了 emoji 和生僻字虽然出租系统用不到但这是通用的好习惯。3.3 如果老师要求“数据可视化”加一张图表统计表很多毕设题目会写“实现数据统计功能”一张dashboard_stats表就能让你在答辩时展示一个简单的趋势图。订阅它记录每日的房源总数、已租数量、当月租金收入、到期合同数。用一个定时任务或者每次操作时顺便更新这张表前端给你接 ECharts 或者 Chart.js加载最近 30 天的数据画折线图。这张表不需要做复杂聚合查询直接 SELECT 出来填到图表里。比你在 SQL 里写GROUP BY date要高效得多也更容易让老师听懂。4. 核心模块实现从登录到租赁合同的全流程编码4.1 用户登录与个人信息展示页面Session 是唯一凭证先写登录模块因为它是所有管理功能的前置。登录页我建议做一次“记住我”的功能但这里有个常见的坑不要用 Cookie 存密码哪怕加密了也别存。正确做法是 Cookie 里存一个随机生成的 token用户在数据库里存 token 的哈希下次登录用 token 换取 session。但这个复杂度对毕设来说有点高了直接用 Session 就够了。// LoginServlet.java 核心逻辑 WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); // 1. 基础校验 if (username null || password null || username.trim().isEmpty() || password.trim().isEmpty()) { request.setAttribute(error, 用户名和密码不能为空); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } // 2. 调用业务层验证 AdminUser user userService.login(username.trim(), password); if (user ! null) { // 3. 登录成功把用户信息存入 Session HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); // 30分钟过期 response.sendRedirect(request.getContextPath() /house/list); } else { // 4. 登录失败回到登录页并提示 request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }逻辑说明userService.login()里会用 MD5 加盐的方式校验密码盐可以用用户名本身。这样即使数据库泄露明文密码也不暴露。登录成功之后用sendRedirect而不是forward防止用户刷新页面时重复提交登录表单。session.setMaxInactiveInterval(30 * 60)设置 30 分钟无操作自动过期这是 Session 的一个必要保护。个人信息展示页面就是热词里提到的“jsp个人信息展示页面”我一般把它做成登录后的首页卡片展示当前登录用户的用户名、姓名、角色、最后登录时间。这里不查询数据库而是从 Session 里把loginUser对象取出来直接渲染减少一次无谓的查询。%-- webapp/WEB-INF/jsp/profile.jsp --% % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle个人信息/title/head body div classprofile-card h3管理员信息/h3 p用户名${sessionScope.loginUser.username}/p p姓名${sessionScope.loginUser.realName}/p p角色${sessionScope.loginUser.role 1 ? 超级管理员 : 普通管理员}/p /div /body /html这里用 EL 表达式${sessionScope.loginUser.username}直接取值避免了页面里写% %脚本片段的丑陋代码。你要养成习惯JSP 里尽量只写 JSTL 和 EL不写 Java 代码在 IDEA 里把“禁止脚本片段”的检查开起来写多了自然会慢慢理解 Servlet 的作用。4.2 房源管理列表分页与状态流转房源列表是整个系统最核心的页面。这里有一个必做的优化分页查询。如果房源只有几十条不分页也无所谓但是老师会问“数据量大了怎么办”你提前做了分页等于是把答辩题提前背好了。分页的关键是 SQL 的LIMIT offset, size和前台页码的联动。// HouseDao.java 分页查询 public ListHouse findPage(int pageNum, int pageSize, String keyword) { String sql SELECT * FROM house WHERE address LIKE ? OR house_no LIKE ? ORDER BY id DESC LIMIT ?, ?; String like % keyword %; return jdbcTemplate.query(sql, new Object[]{like, like, (pageNum - 1) * pageSize, pageSize}, new BeanPropertyRowMapper(House.class)); } // 同时查总记录数用于计算总页数 public int count(String keyword) { String sql SELECT COUNT(*) FROM house WHERE address LIKE ? OR house_no LIKE ?; return jdbcTemplate.queryForObject(sql, Integer.class, % keyword %, % keyword %); }参数说明LIMIT ?, ?两个参数第一个是偏移量第二个是每页条数。(pageNum - 1) * pageSize是标准写法比如第 2 页每页 10 条偏移量就是 10。不要用LIMIT 10 OFFSET 20的写法虽然等价但LIMIT 偏移量, 条数更常见容易看懂。BeanPropertyRowMapper要求实体类字段名和数据库列名驼峰对应比如house_no要映射到houseNo。你可以在 SQL 里用别名house_no AS houseNo来规避这个麻烦。分页条目的 JSP 部分我这里贴一个核心片段。%-- 分页导航 --% div classpagination c:if test${page.pageNum 1} a href${pageContext.request.contextPath}/house/list?pageNum${page.pageNum - 1}keyword${keyword}上一页/a /c:if c:forEach begin1 end${page.totalPages} vari c:choose c:when test${i page.pageNum} span classcurrent${i}/span /c:when c:otherwise a href${pageContext.request.contextPath}/house/list?pageNum${i}keyword${keyword}${i}/a /c:otherwise /c:choose /c:forEach c:if test${page.pageNum page.totalPages} a href${pageContext.request.contextPath}/house/list?pageNum${page.pageNum 1}keyword${keyword}下一页/a /c:if /div分页逻辑里容易翻车的是总页数计算。总页数 总记录数 / 每页条数但必须向上取整。Java 里用(totalCount pageSize - 1) / pageSize这个公式一定要记住Math.ceil处理浮点会有精度问题整数运算反而更稳。关键字回显时要注意特殊字符转义用户在搜索框输入%或者_会被当成 SQL 通配符这时候要用StringEscapeUtils.escapeSql或者手动替换\转义。房源状态流转就是新增房源时状态为“待出租”签约成功后自动改为“已出租”合同到期后管理员手动改回“待出租”。你可以在ContractService.signContract()里加一个事务先插入合同再更新房源状态。如果直接操作数据库不改代码合同和房源状态就容易不一致这是出租系统里最可怕的业务 Bug。4.3 租客模块注册与委托管理的边界租客信息一般由管理员后台录入不开放前台注册。但标题里的热词提到了“jsp modeled2 思想实现用户注册功能”这个是 Servlet 规范里 Model 2 的经典命题JSP 只做视图Servlet 做控制器JavaBean 做模型。把房东和租客分为两个角色时可以让租客通过一个简单的注册页面自己填写然后由管理员审核生效。注册页面的关键字段手机号要做唯一校验身份证号要校验长度和格式18 位最后一位可能是 X。前端用 JavaScript 校验格式后端 Servlet 里再校验一次两重校验缺一不可。前端校验是提升体验后端校验才是安全底线。一条血泪经验就是永远不要相信前端传过来的任何参数包括长度、类型、非空后端全部重查一遍。// TenantRegisterServlet.java 片段 public void doPost(HttpServletRequest request, HttpServletResponse response) { String name request.getParameter(name); String phone request.getParameter(phone); String idCard request.getParameter(idCard); // 后端二次校验 if (name null || name.trim().isEmpty()) { response.getWriter().write({\code\:400,\msg\:\姓名不能为空\}); return; } if (!phone.matches(^1[3-9]\\d{9}$)) { response.getWriter().write({\code\:400,\msg\:\手机号格式不正确\}); return; } // 调用 Service 层手机号唯一性校验在 DAO 层完成 boolean success tenantService.registerTenant(name.trim(), phone.trim(), idCard); if (success) { response.getWriter().write({\code\:200,\msg\:\注册成功\}); } else { response.getWriter().write({\code\:500,\msg\:\手机号已存在\}); } }这里的 Ajax 写法是$.post(tenant/register, data, function(res){...})后端返回 JSON。如果你习惯用PrintWriter输出 JSON记得设置response.setContentType(application/json;charsetUTF-8)否则前端拿到的会是乱码。4.4 合同与账单事务保证房屋状态不漂移合同模块是整个系统里最容易出 Bug 的地方。签约时你做两件事插入一条合同记录把房源状态改成“已出租”。这两个操作必须在一个事务里否则插入合同成功但房源状态没更新这套房子就会被重复签约。我用一个Connection手动管理事务而不是直接用框架的声明式事务这样你能更清楚地看到事务的边界。public boolean signContract(Contract contract) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 插入合同 ContractDao contractDao new ContractDao(); contractDao.insert(conn, contract); // 2. 更新房源状态为已出租 HouseDao houseDao new HouseDao(); houseDao.updateStatus(conn, contract.getHouseId(), 1); // 3. 生成首期账单 PaymentDao paymentDao new PaymentDao(); paymentDao.generateFirstBill(conn, contract); conn.commit(); return true; } catch (Exception e) { conn.rollback(); return false; } finally { DBUtil.close(conn); } }注意new ContractDao()的时候要传入Connection对象而不是在 DAO 内部自己再拿一个连接。否则两个 DAO 各用各的连接事务就失效了回滚也不会生效。这种“事务里各用各连接”的问题非常隐蔽代码看着没问题但一旦中间报错数据就会留下一半。你最好在DBUtil里用一个ThreadLocal来管理连接保证同一个线程内拿到的都是同一个 Connection。账单模块相对简单每月生成的租金账单在合同生效后按付款方式自动生成。比如押一付三签约时生成第一期 3 个月租金的账单之后每季度生成一期。用定时任务还是手动触发毕设阶段手动触发就够了在合同列表里加一个“生成账单”按钮。如果你要做定时任务推荐java.util.Timer或者 Quartz但这就属于进阶内容了。5. 源码打包与部署从 IDEA 到 Tomcat 的避坑指南5.1 为什么你的 JSP 项目在别人电脑上跑不起来源码打包是“基于 JSP 的房屋出租管理系统”这个标题里最实的部分。很多同学代码写得漂亮但交付给老师或同学后对方打开项目不是报 ClassNotFoundException就是数据库连接失败或者 JSP 页面 500。问题的根源不是你代码不行而是打包时漏了环境依赖。一个完整可运行的源码包必须包含这几样东西数据库建表脚本house_rental.sql、项目源码含 pom.xml 或 .classpath、部署说明文档README、可选的 WAR 包或导出的 WAR 文件、Tomcat 版本说明、JDK 版本说明。我见过太多人只发一个压缩包里面全是.idea和target目录源码却忘在src里。整理源码包时第一件事就是删除 IDE 生成的文件。# .gitignore 文件内容 target/ .idea/ *.iml .DS_Store *.log用 Maven 的package命令构建时target目录会自动生成里面那个.war文件就是要交付给 Tomcat 的成果。如果你用 Eclipse右键项目 → Export → WAR file效果相同。5.2 数据库连接配置一个参数写错全军覆没数据库是源码包部署的第二大重灾区。你本地连的是localhost:3306对方的 MySQL 可能端口是3307密码也不是你的root/123456。所以连接配置必须集中在db.properties里并在 README 里大字提示“请根据本地 MySQL 修改此文件”。# src/main/resources/db.properties jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/house_rental?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456参数说明serverTimezoneAsia/Shanghai一定要加MySQL 8.0 驱动默认时区是 UTC不指定的话你插入的时间会比北京时间早 8 个小时。useSSLfalse是本地开发用的不加的话 MySQL 8.0 会警告 SSL 连接未配置。如果对方用的是 MySQL 8.0 驱动com.mysql.jdbc.Driver要改成com.mysql.cj.jdbc.Driver旧驱动类名在 8.0 里已经废弃启动时直接报类找不到。至于 JDBC 工具类写法基本都一样Class.forName(driver)加载驱动DriverManager.getConnection(url, username, password)拿连接封装成一个静态方法DBUtil.getConnection()。5.3 部署步骤WAR 包放哪里、如何验证放行打包完成后部署到 Tomcat 有两条路。路径一把house-rental.war复制到 Tomcat 的webapps目录启动bin/startup.batWindows或bin/startup.shLinuxTomcat 会自动解压并部署。路径二用 IDEA 或 Eclipse 配置 Tomcat 服务器运行适合开发调试验证。我强烈建议你学会路径一因为这是交付时老师会用的方式。部署的关键是确认端口号和访问路径。Tomcat 默认端口是 8080如果被占用要修改conf/server.xml里的Connector port8080 .../。访问路径默认是 WAR 包文件名假设你打成的包叫house-rental.war访问地址就是http://localhost:8080/house-rental/login.jsp。验证是否部署成功不要只看“Tomcat 启动了”要盯着日志。logs/catalina.out或logs/localhost.2025-06-01.log里如果出现Deployment of web application archive [house-rental.war] has finished才是真正成功。如果你用了 JSTL 标签库但没把jstl.jar和standard.jar打进去页面会报org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved这个错误极其常见。你把 Maven 的pom.xml里 JSTL 依赖加好或者把这两个 jar 放进WEB-INF/lib问题立刻消失。5.4 常见部署问题现象、原因、解决方案写几条我这么多年见到的问题新手几乎都会遇到。现象 1访问首页报 404但 Tomcat 启动了。 原因URL 路径不对或者项目没部署到webapps下。 解决先访问http://localhost:8080/确认 Tomcat 自带首页能开再确认 WAR 包名和访问路径一致最后看logs/catalina.out有没有 Deploy 成功信息。现象 2登录后跳转列表页刷新一下变成 405 或表单重复提交。 原因用了forward跳转而不是sendRedirect列表页的doPost会再次执行。 解决登录成功使用sendRedirect列表页的 Servlet 只用doGet并且用WebServlet(/house/list)映射。注意 JSP 里表单提交到 Servlet 时action一定要写post或者get不要空着默认成get。现象 3JSP 页面中文全部变成问号。 原因文件编码和页面编码不一致。IDEA 默认文件编码是 UTF-8但db.properties或 JSP 里写的是GBK。 解决统一设置 IDEA 为 UTF-8JSP 顶部声明% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %数据库连接 URL 加characterEncodingutf8。三处同步改完后重启 Tomcat 并清浏览器缓存。现象 4导入源码后IDEA 报找不到javax.servlet.http.HttpServlet。 原因项目缺少 Tomcat 依赖库。 解决IDEA 里 File → Project Structure → Libraries → 添加 Tomcat 的lib目录Maven 项目则在pom.xml里加上provided作用域的 servlet-api 依赖dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependencyscopeprovided的含义是“编译时需要但打包时不需要”因为 Tomcat 已经提供了这些 jar。如果你不小心打进去了WAR 包运行时可能和你本地 Tomcat 版本冲突导致诡异的NoClassDefFoundError。6. 进阶与验证把毕设从“能跑”提升到“经得起追问”6.1 用WebServlet注解替代web.xml配置现在写 Servlet 尽量不要在web.xml里一个servlet、一个servlet-mapping地手动配置了。用注解可以极大减少配置量。WebServlet(name HouseServlet, urlPatterns {/house/list, /house/add, /house/edit}) public class HouseServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { String path request.getServletPath(); if (/house/list.equals(path)) { ... } else if (/house/add.equals(path)) { ... } } }你的 Servlet 如果只有一个方法可以用WebServlet(/house/*)做路径匹配然后在doGet内部根据自己的request.getPathInfo()判断去哪个方法。这个写法的好处是减少类文件数量坏处是代码分支逻辑变多。对于毕设来说一个 Servlet 对应一个功能 URL 类是最清晰易讲的。遇到要求“不允许使用注解必须用 web.xml”的老师你再把web.xml版本的配置补上也不难。6.2 增加一个“操作日志”拦截器回答“系统如何审计”很多业务系统会要求记录谁在什么时间做了什么操作。实现方式有两种一种是朴素版的在每个 Servlet 里手动写日志另一种是通用版的用Filter或HandlerInterceptor统一拦截请求记录用户的访问路径、请求参数和耗时。我用一个Filter来实现登录校验 日志记录的双重功能。WebFilter(urlPatterns /*) public class AuthFilter 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.contains(/login) || uri.contains(/static/) || uri.endsWith(register.jsp)) { chain.doFilter(request, response); return; } HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { // 未登录重定向到登录页 response.sendRedirect(request.getContextPath() /login.jsp); return; } long start System.currentTimeMillis(); chain.doFilter(req, resp); long cost System.currentTimeMillis() - start; // 记录访问日志耗时超过 500ms 的请求需要关注 System.out.println([ request.getMethod() ] uri 耗时: cost ms); } }这个过滤器在web.xml里的url-pattern一定要配成/*不是/。/*匹配所有路径包括 JSP 页面这样才能拦截到所有请求。request.getSession(false)的false参数至关重要如果为true它会在没有会话时自动新建一个 Session把匿名用户的 Session 也创建出来有安全隐患用false表示“取不到就返回 null”直接重定向到登录页。6.3 验证清单用来检查自己的源码是否达到交付水准写代码是一回事验收是另一回事。我每个项目交出去前都会按以下清单过一遍你需要确保每一项都能回答“是”如果有任何一步卡住就回到相应的章节排查。部署验证在全新环境里执行 [ ] 1. 导入 house_rental.sql 到 MySQL能正常执行无报错 [ ] 2. 修改 db.properties 为用户本地密码能连接成功 [ ] 3. mvn clean package 构建无报错生成 house-rental.war [ ] 4. WAR 包放入 Tomcat webapps启动后无异常日志 [ ] 5. 浏览器访问 http://localhost:8080/house-rental/login.jsp 能打开 功能验证 [ ] 6. 使用 admin/admin123 登录成功错误密码登录失败并给出提示 [ ] 7. 新增房源后列表能显示且页码正确 [ ] 8. 签约合同后房源状态变为“已出租” [ ] 9. 生成租金账单金额与合同首期金额一致 [ ] 10. 退出登录后再访问列表页被重定向到登录页 代码审查 [ ] 11. 不存在明文密码存储至少 MD5 加盐 [ ] 12. JSP 中没有 % % 脚本片段用 JSTL/EL 替代 [ ] 13. SQL 不存在拼接注入全部使用 PreparedStatement [ ] 14. 前后端均有参数校验这个清单也是你答辩时的技术亮点你可以说“我做了一个自动化的手动验收清单覆盖了从环境部署到核心功能再到代码安全三个维度”老师会觉得你不是在糊弄。6.4 最后的收尾手法把脚本放在 README 的第一页源码包的 README 是整个交付物的门面。很多人把它当成跑题结果别人按 README 部署时第一步就卡在“数据库编码不对”。我建议把下面这段“快速启动三步”放在 README 最开头。# 快速启动必读 1. 运行 sql/house_rental.sql创建数据库和表 mysql -uroot -p sql/house_rental.sql 2. 修改 src/main/resources/db.properties 中的账号密码 3. 构建并部署 mvn clean package 将 target/house-rental.war 复制到 Tomcat/webapps 启动 Tomcat访问 http://localhost:8080/house-rental/这段内容看起来简单但很多人为了写“项目介绍”洋洋洒洒几千字结果把部署步骤丢到文末。记住使用者拿到源码包的第一默认动作是“跑起来”。跑不起来再漂亮的介绍也没用。这也是我这么多年最大的一个教训——以前我总把 README 写得像论文摘要后来帮学弟调项目才发现真正有价值的就那几行启动命令其他的统统可以后置。希望这篇实战笔记能帮你把一个 JSP 房屋出租管理系统从设计一路做到可交付希望帮到你。本文还有配套的精品资源点击获取
返回列表