JSP/Servlet机票系统实战:从0到1构建用户登录与购票2大核心功能

发布时间:2026/7/8 19:51:44
JSP/Servlet机票系统实战:从0到1构建用户登录与购票2大核心功能 JSP/Servlet机票系统实战从0到1构建用户登录与购票2大核心功能在Java Web开发领域JSP和Servlet作为经典技术组合至今仍是许多高校教学和企业级项目开发的基础选择。本文将带您深入实战通过构建一个完整的机票预订系统掌握用户认证与会话管理、航班查询与购票流程这两大核心模块的实现方法。1. 开发环境准备与项目初始化1.1 基础环境配置开始之前请确保已安装以下组件JDK 1.8Java开发的基础环境Apache Tomcat 10轻量级Web应用服务器MySQL 5.7关系型数据库Eclipse/IntelliJ IDEA集成开发环境创建动态Web项目的基本目录结构/AirTicketSystem ├── WebContent │ ├── WEB-INF │ │ ├── lib/ # 存放第三方jar包 │ │ └── web.xml # 部署描述文件 │ ├── css/ # 样式表文件 │ ├── js/ # JavaScript文件 │ └── views/ # JSP页面 └── src └── com └── airticket ├── controller # Servlet控制器 ├── dao # 数据访问层 ├── model # 数据模型 └── util # 工具类1.2 数据库设计与初始化创建系统所需的数据库表结构-- 用户表 CREATE TABLE users ( user_id VARCHAR(20) PRIMARY KEY, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), id_card VARCHAR(18), phone VARCHAR(11), email VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 航班信息表 CREATE TABLE flights ( flight_id VARCHAR(10) PRIMARY KEY, flight_no VARCHAR(20) NOT NULL, departure_city VARCHAR(50) NOT NULL, arrival_city VARCHAR(50) NOT NULL, departure_time DATETIME NOT NULL, arrival_time DATETIME NOT NULL, total_seats INT NOT NULL, available_seats INT NOT NULL, price DECIMAL(10,2) NOT NULL ); -- 订单表 CREATE TABLE orders ( order_id VARCHAR(20) PRIMARY KEY, user_id VARCHAR(20) NOT NULL, flight_id VARCHAR(10) NOT NULL, order_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0, -- 0:待支付 1:已支付 2:已取消 passenger_name VARCHAR(50), passenger_id_card VARCHAR(18), FOREIGN KEY (user_id) REFERENCES users(user_id), FOREIGN KEY (flight_id) REFERENCES flights(flight_id) );2. 用户认证模块实现2.1 用户注册功能注册流程设计用户填写注册表单用户名、密码、手机号等服务端验证数据有效性密码加密存储创建用户记录并返回结果实现用户注册的Servlet核心代码WebServlet(/register) public class RegisterServlet extends HttpServlet { private UserDao userDao new UserDaoImpl(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); String phone request.getParameter(phone); // 基本数据验证 if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { request.setAttribute(error, 用户名和密码不能为空); request.getRequestDispatcher(/views/register.jsp).forward(request, response); return; } // 检查用户名是否已存在 if(userDao.checkUsernameExists(username)) { request.setAttribute(error, 用户名已存在); request.getRequestDispatcher(/views/register.jsp).forward(request, response); return; } // 密码加密 String encryptedPwd PasswordUtil.encrypt(password); // 创建用户对象 User user new User(); user.setUserId(IDGenerator.generateUserId()); user.setUsername(username); user.setPassword(encryptedPwd); user.setPhone(phone); user.setCreateTime(new Date()); // 保存用户 boolean result userDao.saveUser(user); if(result) { response.sendRedirect(request.getContextPath() /views/login.jsp?registersuccess); } else { request.setAttribute(error, 注册失败请稍后重试); request.getRequestDispatcher(/views/register.jsp).forward(request, response); } } }2.2 用户登录与会话管理登录验证流程用户提交用户名和密码服务端查询数据库验证凭证创建会话并设置用户标识返回登录结果登录Servlet实现示例WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDaoImpl(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); // 验证码校验实际项目中建议添加 String inputCode request.getParameter(captcha); String sessionCode (String) request.getSession().getAttribute(captcha); if(!inputCode.equalsIgnoreCase(sessionCode)) { request.setAttribute(error, 验证码错误); request.getRequestDispatcher(/views/login.jsp).forward(request, response); return; } User user userDao.findByUsername(username); if(user null || !PasswordUtil.verify(password, user.getPassword())) { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/views/login.jsp).forward(request, response); return; } // 登录成功创建会话 HttpSession session request.getSession(); session.setAttribute(currentUser, user); // 重定向到首页 response.sendRedirect(request.getContextPath() /home); } }安全提示实际项目中应实施以下安全措施密码必须加密存储推荐使用BCrypt实施CSRF防护对登录尝试进行限流敏感操作需二次验证2.3 会话过滤器实现为防止未登录用户访问受限资源需实现过滤器WebFilter(/*) public class AuthenticationFilter implements Filter { private static final SetString ALLOWED_PATHS Set.of( /login, /register, /static/ ); public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; String path request.getRequestURI().substring(request.getContextPath().length()); boolean allowedPath ALLOWED_PATHS.stream().anyMatch(path::startsWith); if (allowedPath || request.getSession().getAttribute(currentUser) ! null) { chain.doFilter(req, res); } else { response.sendRedirect(request.getContextPath() /views/login.jsp); } } }3. 航班查询与购票模块3.1 航班数据查询实现航班查询功能要点支持按出发地、目的地、日期查询支持分页显示结果按起飞时间排序FlightDao接口定义public interface FlightDao { ListFlight searchFlights(String departure, String arrival, Date date); Flight findById(String flightId); boolean updateAvailableSeats(String flightId, int change); }Servlet处理航班查询请求WebServlet(/flight/search) public class FlightSearchServlet extends HttpServlet { private FlightDao flightDao new FlightDaoImpl(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String departure request.getParameter(departure); String arrival request.getParameter(arrival); String dateStr request.getParameter(date); SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd); Date date null; try { date sdf.parse(dateStr); } catch (ParseException e) { date new Date(); // 默认当天 } ListFlight flights flightDao.searchFlights(departure, arrival, date); request.setAttribute(flights, flights); request.getRequestDispatcher(/views/flight_list.jsp).forward(request, response); } }3.2 机票预订流程实现购票业务流程用户选择航班填写乘客信息生成订单待支付状态支付成功后更新订单状态减少航班可用座位数订单创建Servlet示例WebServlet(/order/create) public class CreateOrderServlet extends HttpServlet { private OrderDao orderDao new OrderDaoImpl(); private FlightDao flightDao new FlightDaoImpl(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(); User user (User) session.getAttribute(currentUser); String flightId request.getParameter(flightId); String passengerName request.getParameter(passengerName); String passengerIdCard request.getParameter(passengerIdCard); // 验证航班信息 Flight flight flightDao.findById(flightId); if(flight null || flight.getAvailableSeats() 0) { request.setAttribute(error, 所选航班不可用); request.getRequestDispatcher(/views/flight_list.jsp).forward(request, response); return; } // 创建订单 Order order new Order(); order.setOrderId(IDGenerator.generateOrderId()); order.setUserId(user.getUserId()); order.setFlightId(flightId); order.setPassengerName(passengerName); order.setPassengerIdCard(passengerIdCard); order.setStatus(0); // 待支付 // 事务处理 Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 保存订单 boolean orderSaved orderDao.saveOrder(order, conn); // 更新航班座位 boolean seatUpdated flightDao.updateAvailableSeats(flightId, -1, conn); if(orderSaved seatUpdated) { conn.commit(); response.sendRedirect(request.getContextPath() /order/detail?id order.getOrderId()); } else { conn.rollback(); request.setAttribute(error, 下单失败请重试); request.getRequestDispatcher(/views/flight_list.jsp).forward(request, response); } } catch (SQLException e) { if(conn ! null) { try { conn.rollback(); } catch (SQLException ex) {} } throw new ServletException(数据库操作失败, e); } finally { DBUtil.closeConnection(conn); } } }3.3 订单支付与状态更新支付接口伪代码实现WebServlet(/order/pay) public class PayOrderServlet extends HttpServlet { private OrderDao orderDao new OrderDaoImpl(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String orderId request.getParameter(orderId); HttpSession session request.getSession(); User user (User) session.getAttribute(currentUser); // 验证订单归属 Order order orderDao.findById(orderId); if(order null || !order.getUserId().equals(user.getUserId())) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 无权操作此订单); return; } // 模拟支付处理 boolean paySuccess simulatePayment(order); if(paySuccess) { boolean updated orderDao.updateOrderStatus(orderId, 1); // 已支付 if(updated) { response.sendRedirect(request.getContextPath() /order/detail?id orderId); } else { request.setAttribute(error, 订单状态更新失败); request.getRequestDispatcher(/views/order_pay.jsp).forward(request, response); } } else { request.setAttribute(error, 支付失败请重试); request.getRequestDispatcher(/views/order_pay.jsp).forward(request, response); } } private boolean simulatePayment(Order order) { // 实际项目中接入第三方支付接口 return new Random().nextBoolean(); // 模拟随机成功/失败 } }4. 系统部署与优化4.1 Tomcat 10配置要点关键配置项连接池配置context.xml会话超时设置web.xml字符编码过滤器Gzip压缩配置示例context.xml配置Context Resource namejdbc/airticketDB authContainer typejavax.sql.DataSource maxTotal100 maxIdle30 maxWaitMillis10000 usernamedbuser passworddbpass driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/airticket?useSSLfalseamp;serverTimezoneUTC/ /Context4.2 性能优化建议数据库优化为常用查询字段添加索引使用连接池管理数据库连接合理设计SQL语句避免全表扫描Web层优化启用静态资源缓存使用JSTL减少JSP中的Java代码合理设置HTTP缓存头代码层面优化避免在Servlet中直接编写业务逻辑使用DTO模式减少数据传输量实施适当的异常处理策略4.3 常见问题排查问题1数据库连接泄露现象系统运行一段时间后无法获取数据库连接 解决方案确保所有Connection在使用后正确关闭使用try-with-resources语法配置连接池的泄漏检测问题2中文乱码现象表单提交的中文显示为乱码 解决方案配置字符编码过滤器确保数据库使用UTF-8编码JSP页面添加% page contentTypetext/html;charsetUTF-8 %问题3会话失效现象用户登录后不久需要重新登录 解决方案检查web.xml中的session-timeout设置确保服务器时间正确避免在URL中传递session id5. 项目扩展与进阶5.1 功能扩展建议多乘客同时购票修改订单表结构支持多个乘客实现批量座位锁定机制前端动态添加乘客表单票价动态计算考虑季节因素、提前预订天数等实现价格计算规则引擎添加优惠券/积分抵扣功能第三方登录集成实现OAuth2.0协议集成支持微信、支付宝等快捷登录用户信息自动同步机制5.2 技术演进路径从JSP到现代前端框架保留Servlet作为后端API前端逐步替换为Vue/React实现前后端分离架构引入框架支持逐步引入Spring框架管理Bean使用MyBatis替代原生JDBC添加Spring Security进行权限控制微服务化改造按业务拆分微服务用户服务、航班服务、订单服务引入Spring Cloud生态组件实现服务注册发现与API网关5.3 项目实战经验分享在实际开发机票系统时有几个关键点需要特别注意并发控制当多个用户同时预订同一航班的最后几张票时需要确保数据一致性。我们采用了数据库乐观锁版本号控制结合应用层排队机制来解决这个问题。事务管理创建订单涉及多个表的更新操作必须保证事务的原子性。我们使用了JDBC的编程式事务管理确保在异常情况下能够正确回滚。性能优化航班查询是高频操作我们通过添加合适的数据库索引、引入缓存机制如Redis缓存热门航线数据显著提升了查询响应速度。移动端适配随着移动用户增多我们调整了前端布局使其更好地适应小屏幕设备并开发了精简版的移动端页面。