☰
JSP在线家政网课设全解析:从架构选型到订单权限实战
2026/10/4 9:00:51 网站建设 项目流程

简介:基于JSP的在线家政网设计与实现文档是一份完整的课程设计与毕业设计参考方案,重点面向Web开发方向的在校学生,以及需要快速搭建家政服务平台的开发者。系统主要覆盖家政预约、家教预约、在线求职申请以及个人后台查询申请状态等核心功能,整体采用B/S模式,使用JSP技术完成页面生成与业务处理,以SQL Server 2008作为后台数据库,实现概念结构、逻辑结构和物理结构三个层面的设计,并通过MyEclipse平台进行项目开发与调试。压缩包内为单个docx文档,大小约1.02MB,内容包含项目开发背景与意义、可行性分析、总体设计原则、结构化分析、各模块功能设计、数据库表设计及目录结构等章节,能够完整呈现该系统从需求分析到设计实现的全过程,适合作为同类型课题的设计蓝本,也可为毕业设计说明书或课程论文提供现成的组织框架和文字素材。该资源目前已有126人学习下载,对于正在完成JSP家政类网站选题或希望快速梳理B/S项目开发流程的读者,具有直接参考价值。

1. 在线家政网是个什么项目:JSP技术栈里最耐打的课设选题

如果说Spring Boot是现在Java Web的默认开场,那JSP对于在线家政网这种以表单、列表、订单状态流转为主的中小型管理系统,依然是开发周期最短的选项之一。这个标题背后是一套完整的课程设计或毕业设计原型:用户注册登录、服务类别展示、家政人员信息、预约下单、订单流转、后台管理,全部跑在JSP+Servlet+MySQL这条经典链路上。它解决的不仅是"怎么把页面做出来",更是"一个带角色的业务系统怎么闭环"。

适合三类人:需要交毕设或课设的在校生,想快速搭一个内部演示系统的小团队开发,以及想彻底搞懂Java Web请求链路的新手。JSP 在这个场景里被诟病"过时",但它把页面渲染和数据显示粘在一起,单人开发时确实省事。接下来的内容按我做过同类方案的经验拆开讲,从选型到建库、代码、踩坑,一次性说清。

2. 选型与架构:为什么这个系统用JSP反而是最优解

2.1 JSP在课设和内部项目中的真实定位

很多人在选题时会纠结:都2024年了,为什么还用JSP?答案是家政网这类项目对技术栈的要求是"能快速实现CRUD、能演示、能答辩",而不是"能承受高并发"。在线家政网的业务核心是用户下单、管理员派单、服务人员接单,本质是一套多角色的信息管理系统。JSP页面可以直接嵌Java代码,配合Servlet做控制层,不用引入Spring的依赖注入和事务管理,开发心智负担低得多。

我见过太多人用Spring Boot做课设,结果光搭环境就花了三周,最后页面没写几张。倒不是说Spring Boot不好,而是它对一个单人开发、周期两个月、要写论文的项目来说属于过度设计。JSP的另一个优势是便于现场演示:修改JSP后Tomcat热部署就能看到效果,不用像前后端分离那样同时开两个服务。这个项目如果放在真实生产环境,JSP确实不是主流,但它的价值在于快速验证业务逻辑,而业务逻辑恰恰是家政网这类系统最值得花时间的部分。

2.2 分层架构与请求流转:从index.jsp到数据库的完整路径

在线家政网的典型分层是五年之前Java Web课设就固定下来的四层结构:JSP负责视图渲染,Servlet负责接收请求和跳转,Service层写业务规则,DAO层用JDBC访问MySQL。我在做这类系统时会再加一个Entity层放实体类,一个Util层放数据库连接工具和过滤器,这样代码结构更清晰,答辩时也好讲。

一个用户登录的请求流转路径大致是这样的:浏览器在index.jsp输入用户名密码,表单提交到LoginServlet;Servlet调用UserService的login方法,Service内部再调UserDAO的findByUsername方法;DAO执行PreparedStatement查询并返回User对象;Service把结果返回给Servlet;Servlet将User对象塞进session,然后forward到home.jsp;home.jsp用EL表达式${sessionScope.user.username}把用户名渲染到页面。整条链路里,JSP只负责最后一步的展示,业务判断全部收在Servlet和Service里。我一般建议把Servlet的路径统一设计成/servlet/LoginServlet这种带前缀的格式,配合Filter做权限拦截时非常好写匹配规则。

2.3 角色权限与项目目录结构:先想清楚谁能干什么

家政网至少有三个角色:普通用户(下单方)、服务人员(接单方)、管理员(审核、派单、统计)。角色设计直接决定表结构和Filter怎么写。最简单的设计是用一张user表加role字段,0代表普通用户,1代表服务人员,2代表管理员。前端JSP根据session里存的user.role判断显示哪些入口,比如管理员才能看到"用户管理"菜单。

目录结构我推荐按职责分包:src下建com.housekeep.servlet、com.housekeep.service、com.housekeep.dao、com.housekeep.entity、com.housekeep.util;WebContent下建admin、user、common三个子目录,分别放后台管理页、用户端页面、公共组件(头部、底部)。WEB-INF目录放web.xml和依赖的jar包,之前踩过被扫到WEB-INF下JSP文件直接被Tomcat拒访问的坑,所以所有页面都不要放WEB-INF里,除非你想用Servlet forward来访问。

2.4 权限拦截的Filter写法:没有它你的系统等于裸奔

权限控制是这类系统最容易偷懒的部分,却是答辩评委最爱问的部分。我的建议是做一个LoginFilter,拦截所有需要登录才能访问的Servlet和JSP。拦截规则用/servlet/*和/user/*两个路径,再放行登录Servlet、注册Servlet和静态资源。

这个Filter里要做三件事:从session里取user对象,取不到就重定向到login.jsp;取到了再判断当前请求需要的角色和user.role是否匹配,不匹配就跳转到401提示页。有一个细节容易忽略:如果用户访问的是登录页本身,再把他重定向到登录页会形成302循环,所以Filter里必须先判断请求路径是否等于login.jsp,是就直接放行。这个坑我在做第一个版本时踩过,当时整个登录接口全部302跳转,排查了半天才发现是Filter把自己拦了。

3. 环境搭建与数据库设计:从零到能跑的最小闭环

3.1 开发环境版本搭配:JDK8+Tomcat8.5是保平安组合

JSP项目对环境版本极其敏感,不是"越新越好"而是"越配套越好"。我建议的组合是JDK 1.8 + Tomcat 8.5 + MySQL 5.7,IDE用Eclipse或IDEA都行。这个组合最稳妥的原因在于JDK 8和Tomcat 8.5的兼容性经过了十几年验证,网上能找到的JSP课设代码基本都跑在这套环境上。

千万不要图新鲜用JDK 17搭配Tomcat 10,因为Tomcat 10把javax.servlet包名改成了jakarta.servlet,老项目里的import语句会全部报错,这是JSP课设最常见的翻车点。如果你只有JDK 17,那至少要把Tomcat固定到9.0.x版本,它仍然用javax包名,只是对较新JDK的支持没那么好。IDE里部署时注意Web Content目录要指向项目的WebContent文件夹,部署的Context Path和项目名保持一致,这个路径在之后所有页面的跳转中都会用到。

3.2 建库建表:四张核心表支撑起整个业务流程

在线家政网的表结构不需要复杂,五张表足够:用户表、服务类别表、服务人员表、订单表、评价表。这里给出一份可以抄作业的建表SQL,字符集统一用utf8mb4,排序规则用utf8mb4_general_ci,避免中文乱码。

CREATE DATABASE IF NOT EXISTS housekeep DEFAULT CHARACTER SET utf8mb4; USE housekeep; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT 'MD5加密后的密码', real_name VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', phone VARCHAR(20) DEFAULT NULL, address VARCHAR(200) DEFAULT NULL, avatar VARCHAR(200) DEFAULT NULL COMMENT '头像图片路径', role TINYINT NOT NULL DEFAULT 0 COMMENT '0普通用户 1服务人员 2管理员', status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0禁用', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='用户表'; CREATE TABLE t_service_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '保洁/保姆/月嫂/维修', base_price DECIMAL(10,2) NOT NULL COMMENT '起步价', description VARCHAR(500) DEFAULT NULL, sort INT DEFAULT 0 COMMENT '排序字段,小的在前' ) ENGINE=InnoDB COMMENT='服务类别表'; CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单编号,时间戳+随机数', user_id INT NOT NULL, service_id INT NOT NULL COMMENT '服务人员的用户ID', category_id INT NOT NULL, service_time DATETIME NOT NULL COMMENT '预约上门时间', address VARCHAR(200) NOT NULL, price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1待服务 2服务中 3已完成 4已取消 5退款', remark VARCHAR(500) DEFAULT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='订单表'; CREATE TABLE t_review ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL COMMENT '关联订单ID', user_id INT NOT NULL, service_id INT NOT NULL, content VARCHAR(500) DEFAULT NULL, score TINYINT NOT NULL DEFAULT 5 COMMENT '1到5分', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='评价表';

订单表的order_no字段一定要设UNIQUE,因为业务上要拿它做查询依据,重复会直接报错。price字段在订单表里单独存一份,目的是把下单那一刻的价格固定下来,服务类别表里的base_price以后调整了也不影响历史订单。status字段用整数而不是字符串,是为了方便在Java里定义常量类,避免订单状态散落在代码各处变成魔法数。

3.3 JDBC连接池与DBUtil封装:别再用DriverManager裸连

如果直接在DAO里用DriverManager.getConnection,每次查询都要创建和销毁连接,家政网这种小项目虽然扛得住,但并发一高就会报"Too many connections"。我当时做的时候直接用c3p0连接池,配置非常简单,不需要额外的容器支持。在src下建一个c3p0-config.xml,内容如下:

<c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/housekeep?useSSL=false&amp;characterEncoding=utf8</property> <property name="user">root</property> <property name="password">123456</property> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> <property name="checkoutTimeout">3000</property> <property name="maxIdleTime">60</property> </default-config> </c3p0-config>

注意jdbcUrl里&符号在XML中必须写成&amp;,这个坑会让连接池配置解析失败,报错信息却非常隐晦。然后封装一个DBUtil类:

package com.housekeep.util; import com.mchange.v2.c3p0.ComboPooledDataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException ignored) {} try { if (stmt != null) stmt.close(); } catch (SQLException ignored) {} try { if (conn != null) conn.close(); } catch (SQLException ignored) {} } }

initialPoolSize设为5、maxPoolSize设为20对课设系统足够,checkoutTimeout设为3000毫秒,意思是拿不到连接时最多等3秒,避免页面长时间卡死。DBUtil关闭连接时要注意顺序,先关ResultSet再关Statement最后关Connection,虽然连接池的close方法实际是归还连接,但释放顺序错了在某些版本下会报警告。

4. 核心功能实现:注册登录、预约下单与个人信息展示

4.1 注册与登录:PreparedStatement防注入,MD5加密别裸存

注册功能的代码不难,但有两个点必须处理:密码加密和SQL注入防护。JSP课设里最常见的写法是直接String sql = "insert into t_user values('" + username + "')",这是最典型的低分代码。我用PreparedStatement和MD5重写一遍注册的DAO方法:

package com.housekeep.dao; import com.housekeep.entity.User; import com.housekeep.util.DBUtil; import java.security.MessageDigest; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class UserDao { public boolean register(User user) throws Exception { String sql = "INSERT INTO t_user(username, password, real_name, phone, address) VALUES(?,?,?,?,?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, user.getUsername()); ps.setString(2, md5(user.getPassword())); ps.setString(3, user.getRealName()); ps.setString(4, user.getPhone()); ps.setString(5, user.getAddress()); return ps.executeUpdate() > 0; } } public User findByUsername(String username) throws Exception { String sql = "SELECT * FROM t_user WHERE username = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User u = new User(); u.setId(rs.getInt("id")); u.setUsername(rs.getString("username")); u.setPassword(rs.getString("password")); u.setRealName(rs.getString("real_name")); u.setPhone(rs.getString("phone")); u.setAddress(rs.getString("address")); u.setAvatar(rs.getString("avatar")); u.setRole(rs.getInt("role")); return u; } } } return null; } public static String md5(String raw) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(raw.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } }

参数化查询的好处是用户无论输入什么内容,都会被当作字符串值而不是SQL语句的一部分,这是防注入的标准做法。MD5在这里够用,但如果这个系统真要上线,至少要做加盐处理或改用BCrypt,MD5在碰撞攻击面前已经不安全了。登录时先findByUsername查出用户,再比对md5(输入密码)是否等于库里存的password,而不是把查询条件直接拼进SQL里,这样即使密码错误也能区分是"用户不存在"还是"密码不对"。

4.2 登录拦截与Session管理:Filter统一校验,超时时间按需调

登录成功后的核心操作是把User对象放进session,然后区分角色跳转。用户登录跳首页,管理员登录跳后台。我这里用一个LoginFilter统一拦截未登录访问,代码量不大但覆盖所有需要权限的页面:

package com.housekeep.util; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; 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; String path = request.getRequestURI(); if (path.contains("/login.jsp") || path.endsWith(".css") || path.contains("/LoginServlet") || path.contains("/RegisterServlet")) { chain.doFilter(req, resp); return; } HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }

HttpSession对象里不要放太多东西,一个user对象加一个可能需要的cart或临时标记就够了。Session默认超时时间在Tomcat里是30分钟,课设演示时经常出现"刚登录完填个表单就过期跳回登录页"的情况,可以在web.xml里主动调长:

<session-config> <session-timeout>60</session-timeout> </session-config>

注意Filter里用request.getSession(false)而不是getSession(),因为后者在session不存在时会自动创建,导致拦截器永远拦截不到未登录状态。用false的话,session为空就说明没登录了,逻辑才正确。web.xml里还需要注册Filter,配置filter-name和url-pattern为/servlet/*和/user/*,别漏了第二个。

4.3 预约下单:用状态机管理订单,价格永远以服务端为准

下单模块是家政网的业务核心。页面流程是:用户选择服务类别和服务人员,填写上门时间和地址,提交后生成订单并跳转模拟支付页。这里有个安全性细节容易被忽略:前端隐藏域传过来的价格,后端一定不能直接信。正确做法是根据categoryId从数据库重新查base_price,服务次数按小时算的话还要校验服务时长,防止用户手工改表单里的价格字段。

订单编号的生成用时间戳加三位随机数即可,System.currentTimeMillis()拿到19位数字,拼接后就是天然唯一的字符串。订单状态机单独抽一个常量类:

package com.housekeep.entity; public class OrderStatus { public static final int PENDING_PAY = 0; // 待支付 public static final int PENDING_SERVICE = 1; // 待服务 public static final int SERVING = 2; // 服务中 public static final int FINISHED = 3; // 已完成 public static final int CANCELLED = 4; // 已取消 public static final int REFUNDED = 5; // 已退款 }

状态流转我建议只在Service层做,不允许在Servlet里直接setStatus,这样所有状态变化都走同一套校验规则。比如取消订单时,只有status等于0或1的时候才允许取消,如果订单已经是SERVING状态,取消操作要抛业务异常。代码里多写几个if判断不丢人,丢了状态控制才是答辩时被抓住问到底的点。

4.4 个人信息展示页面:图片坐标定位在JSP里的真实玩法

"jsp个人信息展示页面"是检索这个词的同学最常见的刚需。这个页面要展示头像、用户名、真实姓名、手机号、地址和历史订单列表。头像展示有一个路径坑:数据库中存的是相对路径upload/avatar.jpg,页面里必须用${pageContext.request.contextPath}拼接成完整的上下文路径,否则在非根路径部署时图片会404。完整写法是:

<img src="${pageContext.request.contextPath}/${sessionScope.user.avatar}" style="width:80px;height:80px;border-radius:50%;" alt="头像">

"jsp图片如何对坐标定位"这个热搜词其实有两种理解。一种是CSS定位,比如给头像右下角加一个会员徽标,做法是父容器设置position:relative,徽标图片设置position:absolute和left/top像素偏移;另一种是HTML的<map>标签配合usemap做图片热点区域,比如在服务区域图上划分几个可点击的区块,点击跳转对应区域的分类列表。JSP本身不参与任何定位计算,它只负责把图片路径和服务器的动态数据渲染到src属性里。如果是想做地图上的精确坐标,老实说应该用百度地图或高德的JavaScript API,JSP里做这个属于自己给自己找麻烦。

4.5 页面加载刷新与饿了么Element图标:两个前端细节的正确打开方式

检索"jsp页面让加载完后刷新一次"通常发生在两种场景:上传完新头像后,或者订单状态在另一个页面被修改后。实现方式是在<body onload="window.location.reload()">里调用刷新,或者用一个隐藏iframe提交表单、成功后调用parent.location.reload()。但必须加防死循环标记:刷新后的URL会带一个参数?reloaded=1,判断这个参数存在就不再调用reload,否则页面会一直刷到浏览器崩溃。

再说到"饿了么elment图标前端jsp"这个搜索词,问题本质是:Element UI是Vue的组件库,JSP页面无法直接实例化el-icon这样的Vue组件。可行做法是去Element UI的GitHub Release页把fonts/目录和对应的index.css下载下来,放到WebContent的css和fonts目录里,然后通过<link>标签引入,再直接用<i class="el-icon-user">这样的字形图标。但你要明白,图标字形可以拿来用,el-button那些带交互行为的组件在JSP里是模拟不出来的,最多是样式接近。课设阶段老老实实用Bootstrap加Font Awesome,比硬套Element UI省心得多。

5. 避坑指南:我在JSP家政网上踩过的6个翻车现场

5.1 表单提交后中文全部变成问号

现象:注册页面填入"张三",提交到Servlet后在控制台打印出来是"???",再存进数据库也是中文乱码。

原因:POST请求的编码默认是ISO-8859-1,Tomcat接收请求时没有按UTF-8解码;另外如果数据库表字段的字符集不是utf8mb4,存储时也会二次转码失败。

解决:在web.xml里配置一个编码过滤器,或者直接在Servlet第一行写request.setCharacterEncoding("UTF-8")。但最可靠的做法是用CharacterEncodingFilter统一处理,注意这个Filter的执行顺序必须排在LoginFilter之前,否则登录接口本身也是乱码,用户名密码永远验证不过。同时检查MySQ连接的jdbcUrl里带不带characterEncoding=utf8,不带的话即使请求编码对了,写库时仍可能乱码。

5.2 提交表单后404,或者跳转到带项目名的错误路径

现象:点击"登录"按钮后浏览器地址变成了http://localhost:8080/login.jsp,页面直接404,因为Servlet的映射路径没拼上项目上下文。

原因:JSP表单里的action写成了绝对路径/LoginServlet,而部署后的应用在Tomcat下实际路径是/housekeep/LoginServlet,前面少了项目名。另一个常见原因是把response.sendRedirect写成了forward,或者反过来,导致地址栏路径和实际资源路径不匹配。

解决:所有跳转统一使用绝对上下文路径:request.getContextPath() + "/LoginServlet"。JSP页面里写成${pageContext.request.contextPath}/LoginServlet,HTML的form标签里也这么拼。这个坑最隐蔽的地方在于IDE里Run on Server时,Tomcat有时用根路径部署,你本地测着没事,换台机器或用war包部署就全部404,根因就是路径写死了。

5.3 图片上传成功但页面刷新后依然显示旧图或404

现象:个人中心上传新头像,上传提示成功,JSP页面刷新后看到的还是老头像,或者直接裂图。

原因:文件确实写到了磁盘,但写的是IDE的临时发布目录,不是项目源码目录。Eclipse的WebContent会复制到wtpwebapps目录下,IDEA会复制到target/classes,你在IDE里改的文件没同步过去。更隐蔽的是数据库里存的路径还是旧值,上传逻辑只写了文件没更新user表的avatar字段。

解决:上传时把文件写到CommonConstants里配置的绝对路径,比如D:/upload/,然后数据库存upload/xxx.jpg,再在Tomcat的server.xml里配置一个虚拟目录映射,把/upload/**映射到D:/upload/。这样文件与项目解耦,不再受IDE重部署影响。上传完成后必须执行new File(oldPath).delete()删掉旧头像,不然每次上传都会堆积垃圾文件。

5.4 Tomcat启动正常但访问时报ClassNotFoundException或SSL连接错误

现象:启动Tomcat没问题,一访问数据库相关页面就抛java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,或者控制台报Could not create connection to database server。

原因:MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,老代码里写的com.mysql.jdbc.Driver在驱动jar升级后已经废弃,类加载直接失败。另一个高频原因是MySQL 8默认启用SSL,JDBC连接时没加useSSL=false参数,连接过程被TLS握手卡住。

解决:驱动jar包对应版本选择:MySQL 5.7用mysql-connector-java-5.1.49.jar,MySQL 8.0用mysql-connector-java-8.0.x.jar。jdbcUrl统一写jdbc:mysql://localhost:3306/housekeep?useSSL=false&serverTimezone=Asia/Shanghai。URL里的时区参数不能省,MySQL 8的驱动对时区敏感,不指定会报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。还有个边角坑:jar包不要同时放在Tomcat的lib目录和项目的WEB-INF/lib目录,这会导致ClassLoader加载到两个版本的类,偶发异常很难查。

5.5 JSP页面无限刷新停不下来,浏览器直接卡死

现象:给<body>加了onload="location.reload()"实现"加载完后刷新一次"的效果,结果页面不停刷新,CPU占用飙升。

原因:每次reload后页面重新加载,onload事件再次触发,形成了死循环。这是"jsp页面让加载完后刷新一次"这个需求最容易踩的坑,核心问题是刷新动作没有终止条件。

解决:用查询参数做标记。第一次加载时URL不带参数,执行reload并拼上?refreshed=1;第二次加载时页面判断request.getParameter("refreshed")不为空,就跳过reload。还有一种方式是session里放一个布尔标记,刷新一次后置为false,避免URL难看。如果这个刷新的目的是拉取订单状态,更优雅的做法是JSP里放一个自动刷新的meta标签,然后配合后台接口轮询,而不是整页刷新。

5.6 用户表role字段设计成String,导致权限判断永远不相等

现象:登录成功后,管理员跳转后台时报错,或者后台页面明明写死了if(user.role == 2)却始终进不来。

原因:role字段在数据库里是varchar类型,存的是"2",Java里从ResultSet用getInt取值时虽然能自动转成数字,但如果用getString取值再和整数比较,永远false。另一种情况是页面里用了${user.role == 2},EL表达式里整数比较没问题,但硬编码2这个魔法数,后面前端改了显示逻辑就会漏改。

解决:表结构里role用TINYINT,实体类用Integer,比较时统一用OrderStatus那样的常量类做命名引用。数据库里不要存"admin"这种字符串角色,因为字符串没有长度约束容易拼错,而且比较效率低。如果已经用了varchar,DAO层取值时用rs.getInt("role")强制转换,页面端判断顺手加上+ ''的空字符串处理,避免与NULL比较时EL表达式返回空。

6. 验收与进阶:从答辩过关到把它变成加分项的最后一步

拿到一个能做、能跑的在线家政网,并不等于做完。我习惯按角色走一遍全流程来验收:先注册一个普通用户,下单,等支付,再注册一个服务人员账号接单,最后用管理员账号在后台看到订单并标记完成。这六个状态节点全部走通,项目才算闭环。验收时还要做两个破坏性测试:一个是重启Tomcat后数据还在不在,另一个是退出登录后直接敲入订单管理页的URL,看Filter能不能把你踢回登录页。这两条过了,答辩演示就不会掉链子。

进阶方向上,性价比最高的是给后台管理加一张统计页,用JSP显示每日订单数和营收总额,数据源就是刚才的t_order表,一条SQL的事,但演示效果立刻不一样。再往上值得做的是把DAO层从JDBC换成MyBatis,实体类和Service层基本不动,这是答辩被问到"你觉得哪里可以改进"时最标准也最好讲的答案——因为你有旧代码做对比,能说出具体差异。

最后说一个我的习惯做法:数据库建表时就把状态字段的注释写清楚,Java里对应的常量类同步建好再写业务代码。状态机想清楚了,后面所有业务代码都是线性往下写,不用回头改。这套JSP家政网方案做成什么深度,取决于你想让它过还是一个能讲的完整项目。选后者的话,哪怕技术栈旧,把业务闭环和权限边界做扎实了,一样是能拿得出手的东西。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询