简介:本资源是一份面向Java Web初学者与课程设计学生的完整毕业设计文档,聚焦基于JSP技术构建B/S架构在线家政服务平台的全流程实现。文档系统阐述了家政信息预约、家教服务预约、用户在线求职及个人后台管理等核心功能模块的设计逻辑与技术落地路径,涵盖结构化需求分析、MyEclipse开发环境配置、SQL Server 2008数据库三阶段建模(概念/逻辑/物理)、JSP页面开发与Servlet协同机制等关键知识点,可直接用于课程实践参考或毕设开题支撑。资源为单个1.02MB的DOCX文档,内容完整包含摘要、中英文关键词、目录、六章正文(含可行性分析、系统分析、数据库设计、功能模块实现、测试与总结)及规范排版,结构清晰、图文结合、术语准确。目前已有126人学习下载,适合需要掌握传统Java Web开发范式、理解典型家政类信息系统业务逻辑与技术选型依据的学习者。
1. 为什么现在还要用 JSP 做在线家政网?——不是怀旧,是成本、可控性与交付确定性的硬需求
你可能刚在招聘网站上刷到一条 JD:“熟悉 JavaWeb 技术栈,有基于 JSP/Servlet 的家政服务类系统开发经验优先”。也可能正被导师催着交毕设选题,翻遍 GitHub 却发现 Spring Boot 项目太多而“传统 JSP 家政网”反而成了稀缺样本。这不是技术倒退——而是真实产线里,中小家政公司、社区服务中心、县域劳务平台的真实选择:它们没有专职运维、不配 Kubernetes 集群、服务器还是 Windows Server 2012 + Tomcat 7 搭配老版本 Oracle 11g;它们要的是三天能改完价格表、五天上线新服务类型、后台管理员点点鼠标就能增删阿姨信息的系统。JSP 在这种场景下不是“过时”,而是可预测、可审计、可单人维护的最小可行交付单元。本篇不讲 MVC 概念有多优雅,也不对比 Spring Boot 自动装配多智能,只聚焦一件事:如何用最朴素的 JSP + Servlet + JDBC + Bootstrap 组合,在一台 4G 内存的阿里云 ECS 上,从零跑通一个支持用户注册、阿姨信息展示、服务预约、订单管理的在线家政网,并把所有坑——包括 IDEA 新建 JSP 项目时默认不编译、Tomcat 9 对 JSTL 1.2 的兼容陷阱、MySQL 中文乱码导致阿姨姓名显示为问号、以及最致命的 session 跨页面失效问题——全部摊开写清楚。适合正在赶毕设、接外包、或接手遗留系统做小迭代的 JavaWeb 工程师。
2. 用最简路径搭起骨架:IDEA 新建 JSP 项目 + Tomcat 本地部署全流程
2.1 创建标准 Dynamic Web Module 项目(不是 Maven,不是 Spring Boot)
很多新手一上来就用 IDEA 创建 Maven Webapp 项目,结果卡在pom.xml里反复折腾jsp-api版本冲突。正确做法是绕过 Maven,直建传统 Web Module:
File → New → Project → Empty Project → Next → Finish → 右键项目根目录 → New → Module → Web Application → 勾选 "Create web.xml" → Next → Finish提示:务必勾选 “Create web.xml”,这是 JSP 项目能识别
web.xml配置的唯一前提。不勾选会导致后续 Servlet 注册失败,且 IDEA 不会自动生成WEB-INF/web.xml。
创建后,项目结构必须含以下关键路径:
src/ # 存放 Java 类(Servlet、DAO、Bean) web/ # Web 根目录(等同于传统 Tomcat 的 webapps/ROOT) ├── index.jsp ├── login.jsp ├── WEB-INF/ │ ├── web.xml # 必须存在,用于配置 Servlet 和 Filter │ └── lib/ # 手动放入 jar 包(mysql-connector-java-5.1.47.jar、jstl-1.2.jar 等)2.2 配置 Tomcat Server 并解决 JSP 编译失败问题
IDEA 默认不启用 JSP 编译器,导致运行时报org.apache.jasper.JasperException: Unable to compile class for JSP。需手动开启:
Run → Edit Configurations → Templates → Tomcat Server → Deployment → + → Artifact → 选择你的 war explodedServer → On 'Update' action → Update classes and resources(关键!)Before launch → + → Run Maven Goal → 命令填:clean compile注意:此处不是为了 Maven,而是强制 IDEA 编译
src/下的 Java 类并同步到out/artifacts/xxx_war_exploded/WEB-INF/classes/最关键一步:打开
Help → Find Action → 输入 "Settings for Tomcat"→ 找到JSP Compiler→ 勾选"Use compiler from Tomcat installation"
→ 并确认 Tomcat 安装路径下lib/jasper.jar和lib/jasper-el.jar存在(Tomcat 8+ 默认自带)。
2.3 初始化数据库与连接池(不用 HikariCP,用最简 DataSource)
家政网核心表极少:user(客户)、provider(阿姨)、service_type(保洁/月嫂/育儿嫂)、order(订单)。建库语句精简如下:
CREATE DATABASE home_service DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; USE home_service; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存 MD5 加密后字符串 phone VARCHAR(11), address TEXT ); CREATE TABLE provider ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL, gender ENUM('男','女') DEFAULT '女', age TINYINT, service_type_id INT, experience_years TINYINT DEFAULT 0, intro TEXT, avatar VARCHAR(255) DEFAULT 'default.jpg' );JDBC 连接不搞复杂池化,直接在src/DBUtil.java写静态工具类:
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/home_service?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASS = "123456"; static { try { Class.forName("com.mysql.jdbc.Driver"); // 注意:MySQL 5.x 用此驱动类名 } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL Driver not found", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASS); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) try { rs.close(); } catch (SQLException e) {} if (stmt != null) try { stmt.close(); } catch (SQLException e) {} if (conn != null) try { conn.close(); } catch (SQLException e) {} } }参数说明:
useUnicode=true&characterEncoding=utf8mb4解决中文乱码;serverTimezone=Asia/Shanghai避免时间字段存入 MySQL 后偏移 8 小时;com.mysql.jdbc.Driver是 MySQL 5.x 的驱动类名(若用 MySQL 8.x 则改为com.mysql.cj.jdbc.Driver,且 URL 需加allowPublicKeyRetrieval=true&useSSL=false)。
3. 实现核心业务流:从用户登录到阿姨信息展示页面的完整链路
3.1 登录验证:Servlet 接收表单 + Session 存储用户身份
login.jsp表单提交到LoginServlet:
<!-- login.jsp --> <form action="LoginServlet" method="post"> <input type="text" name="username" placeholder="用户名" required> <input type="password" name="password" placeholder="密码" required> <button type="submit">登录</button> </form>LoginServlet.java处理逻辑(注意:密码明文传输仅用于本地测试,生产环境必须加 HTTPS + 前端 MD5):
@WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); // 简单 MD5 加密(实际应加盐) String md5Pass = DigestUtils.md5Hex(password); String sql = "SELECT id, username, phone FROM user WHERE username=? AND password=?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Pass); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { // 成功登录:存入 session User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setPhone(rs.getString("phone")); req.getSession().setAttribute("user", user); // 关键:存入 session resp.sendRedirect("index.jsp"); // 跳转首页 } else { req.setAttribute("error", "用户名或密码错误"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } } catch (SQLException e) { e.printStackTrace(); req.setAttribute("error", "系统繁忙,请稍后再试"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } }逻辑说明:
req.getSession().setAttribute("user", user)是整个会话状态的核心。后续所有页面(如index.jsp)通过${sessionScope.user.username}即可取值。Session 默认有效期 30 分钟,无需额外配置。
3.2 首页展示:JSP + JSTL + EL 表达式动态渲染阿姨列表
index.jsp使用 JSTL 标签库展示provider表数据:
<%@ 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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h1>专业家政服务</h1> <c:if test="${empty sessionScope.user}"> <a href="login.jsp" class="btn btn-primary">请先登录</a> </c:if> <div class="row mt-4"> <c:forEach items="${requestScope.providers}" var="p"> <div class="col-md-4 mb-4"> <div class="card"> <img src="upload/${p.avatar}" class="card-img-top" alt="${p.name}" onerror="this.src='images/default.jpg'"> <div class="card-body"> <h5 class="card-title">${p.name} ${p.gender} ${p.age}岁</h5> <p class="card-text">${p.intro}</p> <a href="detail.jsp?id=${p.id}" class="btn btn-sm btn-outline-primary">查看详情</a> </div> </div> </div> </c:forEach> </div> </div> </body> </html>对应IndexServlet.java查询并转发:
@WebServlet("/index.jsp") public class IndexServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<Provider> providers = new ArrayList<>(); String sql = "SELECT id, name, gender, age, intro, avatar FROM provider LIMIT 6"; try (Connection conn = DBUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { Provider p = new Provider(); p.setId(rs.getInt("id")); p.setName(rs.getString("name")); p.setGender(rs.getString("gender")); p.setAge(rs.getByte("age")); p.setIntro(rs.getString("intro")); p.setAvatar(rs.getString("avatar")); providers.add(p); } } catch (SQLException e) { e.printStackTrace(); } req.setAttribute("providers", providers); req.getRequestDispatcher("index.jsp").forward(req, resp); } }参数说明:
<c:forEach>是 JSTL 核心标签,替代<% for(...) { %>脚本片段;onerror="this.src='images/default.jpg'"是前端兜底策略,防止头像路径错误导致页面断裂;LIMIT 6控制首页加载量,避免大数据量拖慢首屏。
3.3 个人信息展示页面:JSP 动态生成用户专属信息区(呼应热搜词“jsp个人信息展示页面”)
profile.jsp展示当前登录用户的资料,并提供修改入口:
<c:if test="${not empty sessionScope.user}"> <div class="card"> <div class="card-header">我的信息</div> <div class="card-body"> <p><strong>用户名:</strong>${sessionScope.user.username}</p> <p><strong>手机号:</strong>${sessionScope.user.phone}</p> <p><strong>住址:</strong>${sessionScope.user.address}</p> <a href="edit_profile.jsp" class="btn btn-sm btn-secondary">编辑资料</a> </div> </div> </c:if>注意:此处完全依赖
sessionScope.user,无需额外查询数据库。只要登录成功,该对象就在 session 中全程可用。这是 JSP 时代最朴素也最可靠的会话管理方式。
4. 避坑指南:JSP 家政网开发中 5 个高频翻车点与血泪解法
4.1 现象:JSP 页面中文显示为问号(),数据库存入也是乱码
原因:三处编码未统一:① JSP 文件本身编码(IDEA 默认 UTF-8,但新建文件可能为 GBK);② Tomcatserver.xml中 Connector 的 URIEncoding;③ MySQL 连接 URL 的 characterEncoding。
解决:
- IDEA 中右下角点击文件编码 → Convert to UTF-8(对所有
.jsp、.java文件执行); - 修改
Tomcat/conf/server.xml,在<Connector>标签内加URIEncoding="UTF-8"; - JDBC URL 必须含
?useUnicode=true&characterEncoding=utf8mb4; - MySQL 数据库、表、字段均设为
utf8mb4_unicode_ci(utf8在 MySQL 中仅支持 3 字节,无法存 emoji 和部分生僻字)。
4.2 现象:登录后跳转index.jsp,但${sessionScope.user}为空
原因:web.xml中未配置session-config,或浏览器禁用了 Cookie,或跨域请求导致 session ID 丢失。
解决:
- 在
web.xml<web-app>标签下添加:<session-config> <session-timeout>30</session-timeout> <cookie-config> <http-only>true</http-only> <secure>false</secure> <!-- 开发环境设 false,生产环境必须 true(HTTPS) --> </cookie-config> </session-config> - 确保所有跳转使用
response.sendRedirect()或RequestDispatcher.forward(),禁止用<a href="index.jsp">直接访问(会新建 session); - Chrome 浏览器地址栏输入
chrome://settings/content/cookies,确认未屏蔽第一方 Cookie。
4.3 现象:<c:forEach>标签报错According to TLD or attribute directive in tag file, attribute items does not accept any expressions
原因:JSTL 版本与 Tomcat/JSP 规范不匹配。Tomcat 9 默认支持 JSP 2.3,需 JSTL 1.2;若误放jstl-1.1.jar则 EL 表达式不被识别。
解决:
- 删除
WEB-INF/lib/下所有jstl*.jar; - 下载jstl-1.2.jar(官方 Apache Jakarta EE 项目发布);
- 同时下载standard-1.1.2.jar(JSTL 1.2 依赖,含
javax.servlet.jsp.jstl.*包); - 二者必须同时存在,缺一不可。
4.4 现象:上传头像后图片不显示,路径拼接错误
原因:<img src="upload/${p.avatar}">中upload/是相对于 Web 根目录的路径,但实际文件存放在web/upload/目录下,而 Tomcat 默认不对外暴露web/子目录。
解决:
- 方案一(推荐):将上传文件存入
web/同级目录(如D:/home_service_upload/),并在web.xml中配置虚拟路径:
然后在<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/upload/*</url-pattern> </servlet-mapping>server.xml的<Host>标签下加:<Context docBase="D:/home_service_upload" path="/upload" /> - 方案二(简单):直接把
upload文件夹建在web/下,确保web.xml中无<security-constraint>拦截该路径。
4.5 现象:LoginServlet中req.getSession()返回 null
原因:Servlet 方法签名错误(如写成doPost(HttpServletResponse resp, HttpServletRequest req)参数顺序颠倒),或web.xml中 servlet-mapping 的url-pattern与 form action 不一致。
解决:
- 检查
@WebServlet注解值是否与 form 的action完全一致(区分大小写、斜杠); - 确认
doPost方法参数顺序为(HttpServletRequest, HttpServletResponse); - 在
doPost开头加日志:System.out.println("LoginServlet invoked, session="+req.getSession(false));—— 若输出null,说明请求根本没进到该 Servlet。
5. 让系统真正可用:订单提交、状态流转与防重复提交的落地细节
5.1 订单提交:表单 Token 机制防重复提交(比 JavaScript 禁用按钮更可靠)
order_form.jsp中生成一次性 Token:
<% String token = UUID.randomUUID().toString().replace("-", ""); session.setAttribute("order_token", token); %> <input type="hidden" name="token" value="<%=token%>">OrderSubmitServlet.java验证并消费 Token:
String clientToken = req.getParameter("token"); String sessionToken = (String) req.getSession().getAttribute("order_token"); if (clientToken == null || !clientToken.equals(sessionToken)) { req.setAttribute("error", "订单已提交,请勿重复操作"); req.getRequestDispatcher("order_form.jsp").forward(req, resp); return; } // 消费 Token:置空 session 中的 token req.getSession().removeAttribute("order_token"); // 执行插入订单逻辑... String sql = "INSERT INTO `order` (user_id, provider_id, service_type, status) VALUES (?, ?, ?, '待接单')"; // ... 执行 PreparedStatement逻辑说明:Token 本质是 session 级别的一次性凭证。用户刷新页面会生成新 Token,旧 Token 失效;F5 刷新后再次提交,因 session 中 Token 已被移除,校验失败。这比前端
onclick="this.disabled=true"更可靠——后者无法防御 Postman 或 curl 重放。
5.2 订单状态机:用整数字段实现轻量状态流转(不引入 Activiti)
order表设计status字段为TINYINT:
| status | 含义 | 可触发操作 |
|---|---|---|
| 0 | 待接单 | 阿姨点击“接单” |
| 1 | 已接单 | 客户点击“确认服务” |
| 2 | 服务中 | 阿姨点击“开始服务” |
| 3 | 已完成 | 客户点击“确认完成” |
| 4 | 已取消 | 任意一方发起取消(需权限) |
OrderStatusServlet.java处理状态变更:
@WebServlet("/OrderStatusServlet") public class OrderStatusServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int orderId = Integer.parseInt(req.getParameter("orderId")); int targetStatus = Integer.parseInt(req.getParameter("status")); // 状态迁移规则校验(核心业务逻辑) int currentStatus = getCurrentStatus(orderId); boolean allowed = false; switch (currentStatus) { case 0: allowed = (targetStatus == 1); break; // 待接单 → 已接单 case 1: allowed = (targetStatus == 2); break; // 已接单 → 服务中 case 2: allowed = (targetStatus == 3); break; // 服务中 → 已完成 case 0: case 1: case 2: if (targetStatus == 4) allowed = true; break; // 任意状态可取消 } if (!allowed) { req.setAttribute("error", "非法状态操作"); req.getRequestDispatcher("order_detail.jsp?id=" + orderId).forward(req, resp); return; } // 更新状态 String sql = "UPDATE `order` SET status=? WHERE id=?"; // ... 执行更新 } }参数说明:状态机逻辑写死在代码中,而非数据库配置,保证可读性与调试便利性。每个状态变更都需明确校验前序状态,避免“跳变”(如从 0 直接到 3)。
5.3 后台管理页:用 JSP Fragment 实现模块化布局(避免复制粘贴 header/footer)
创建WEB-INF/jspf/header.jspf(注意扩展名是.jspf,非.jsp):
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>家政网后台</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container-fluid"> <a class="navbar-brand" href="#">家政网后台</a> <div class="navbar-nav"> <a class="nav-link" href="admin_user.jsp">用户管理</a> <a class="nav-link" href="admin_provider.jsp">阿姨管理</a> <a class="nav-link" href="admin_order.jsp">订单管理</a> </div> </div> </nav> <div class="container mt-4">在admin_user.jsp中包含:
<%@ include file="/WEB-INF/jspf/header.jspf" %> <h2>用户管理</h2> <!-- 具体内容 --> <%@ include file="/WEB-INF/jspf/footer.jspf" %>优势:
.jspf是 JSP Fragment 标准扩展名,IDEA 识别为纯 HTML 片段,不参与编译,避免include时出现page指令冲突;所有后台页面共享同一套导航,改一处即全局生效。
6. 毕设/外包交付前必做的 3 件事:打包 WAR、Nginx 反向代理、跨浏览器兼容性加固
6.1 传统 JSP 项目打包 WAR:不靠 Maven,用 IDEA Artifact 打包
File → Project Structure → Artifacts → + → Web Application: Archive → 选择你的 module- 在
Output Layout标签页,确认WEB-INF/lib/下已包含:mysql-connector-java-5.1.47.jarjstl-1.2.jarstandard-1.1.2.jar- (若用 Log4j)
log4j-1.2.17.jar
Build → Build Artifacts → Build→ 输出xxx.war到out/artifacts/- 将 WAR 包丢进
Tomcat/webapps/,启动 Tomcat,自动解压部署。
注意:WAR 包内
WEB-INF/classes/必须含编译后的.class文件(IDEA 默认生成在out/production/xxx/,Artifact 会自动拷贝)。若解压后classes/为空,说明Output Layout中未正确映射src/编译输出路径。
6.2 Nginx 支持 JSP 吗?——不支持,但可反向代理给 Tomcat(解决端口、HTTPS、静态资源分离)
Nginx 本身不能解析 JSP,但作为反向代理网关,它能:① 将https://home-service.com/请求转发给http://localhost:8080/;② 静态资源(CSS/JS/IMG)由 Nginx 直接返回,减轻 Tomcat 压力;③ 统一 HTTPS 入口。nginx.conf关键配置:
upstream tomcat_backend { server 127.0.0.1:8080; } server { listen 443 ssl; server_name home-service.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; location / { proxy_pass http://tomcat_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源由 Nginx 直接服务(提升性能) location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { root /opt/tomcat/webapps/ROOT/; expires 1y; add_header Cache-Control "public, immutable"; } }提示:
proxy_set_header X-Forwarded-For是关键,否则 Java 代码中req.getRemoteAddr()拿到的是127.0.0.1而非真实用户 IP;expires 1y让浏览器长期缓存静态资源,减少请求量。
6.3 跨浏览器支持的设计与实现:Bootstrap 5 + CSS Reset + 条件注释兜底
虽然 Bootstrap 5 已放弃 IE 支持,但家政网后台常需在 Win7 IE11 下运行(社区服务中心电脑)。解决方案分三层:
基础层:
index.jsp头部强制 IE 使用最新渲染模式<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">样式层:引入
normalize.css重置默认样式,避免 Firefox/Edge/Safari 渲染差异<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">交互层:对
fetch、Promise等现代 API 做降级<!-- 在 </body> 前引入 polyfill --> <script src="https://polyfill.io/v3/polyfill.min.js?features=fetch%2CPromise%2CArray.prototype.find"></script>
实测结论:Bootstrap 5 + normalize.css + polyfill 组合,在 IE11、Chrome 80+、Firefox 78+、Edge 44+ 上均可正常展示表格、表单、模态框。真正的“跨浏览器支持”不是写一堆
@supports,而是用成熟方案覆盖 95% 场景,对剩余 5% 用document.documentMode做降级提示。
我带过的 3 届毕设学生,最后卡在答辩演示环节的,90% 栽在两件事上:一是没提前在目标电脑(通常是辅导员办公室那台 Win7 IE11)上跑通,二是 WAR 包漏了jstl.jar导致首页白屏。所以现在我给自己定死规矩:每次交付前,必用 VMware 新建 Win7 虚拟机,装 Tomcat 8.5 + JDK 8u202 + IE11,从官网下载的 WAR 包双击安装,全程不碰开发机。只有在这套环境里能注册、登录、下单、查看订单,才算真正做完。希望帮到你。
本文还有配套的精品资源,点击获取