基于JSP+Servlet的在线影院售票管理系统 - 源码深度解析

JavaJavaScriptHTMLCSSMySQLJSP+Servlet
2026-03-113 浏览

文章摘要

本系统是一个基于JSP和Servlet技术栈构建的在线影院售票管理平台,旨在解决传统影院线下售票效率低下、管理流程繁琐的核心痛点。其核心业务价值在于将影院的票务销售与日常运营管理全面数字化,通过一个集成的Web系统,实现电影信息的集中维护、场次座位的动态管理以及线上购票的完整闭环,从而显著提升影院的...

在数字化浪潮席卷各行各业的今天,传统影院的运营模式正面临着巨大的转型压力。线下排队购票不仅消耗顾客宝贵的时间,也给影院的场次安排、座位管理和财务对账带来了繁重的工作量。一个能够整合影片信息、放映场次、在线选座与支付功能的综合性管理平台,成为提升影院竞争力与运营效率的关键工具。本系统——“影幕之巅”在线票务管理系统——正是基于这一背景,采用经典的JSP+Servlet技术栈构建,旨在为中小型影院提供一个功能完备、稳定可靠的数字化解决方案。

系统严格遵循J2EE的MVC设计模式,构建了清晰的三层架构。模型层由一系列封装了业务数据和行为的JavaBean构成,负责与底层数据库进行交互;控制层由Servlet担当,作为系统的中枢神经,接收并解析所有前端HTTP请求,调用相应的业务模型进行处理,并决定下一步的视图跳转;视图层则采用JSP技术,通过嵌入JSTL标签库和EL表达式,动态地渲染数据,生成最终的HTML页面呈现给用户。这种职责分离的设计,确保了代码的高内聚、低耦合,极大地提升了系统的可维护性和可扩展性。

数据库作为系统的基石,其设计的合理性直接决定了应用的性能与稳定性。系统采用了MySQL数据库,并设计了7张核心数据表来支撑整个业务逻辑。以下重点分析其中几个关键表的设计亮点。

movies表是系统的核心之一,它存储了所有影片的元数据。

CREATE TABLE movies (
    movie_id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    director VARCHAR(100),
    actors TEXT,
    plot_description TEXT,
    duration INT COMMENT '时长(分钟)',
    release_date DATE,
    poster_url VARCHAR(500),
    status ENUM('Coming_Soon', 'Showing', 'Removed') DEFAULT 'Coming_Soon',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

该表设计颇具匠心。首先,movie_id作为自增主键,确保了每部影片的唯一标识。status字段使用ENUM类型,明确限制了影片的状态只能为“即将上映”、“正在热映”或“已下架”,通过数据库层面保证了数据的一致性,避免了无效状态的产生。poster_url字段用于存储电影海报的图片链接,为前端展示提供了便利。此外,对可能包含较长文本的actorsplot_description字段使用了TEXT类型,确保了信息的完整存储。

另一张核心表是screening_schedules,它关联了影片、放映厅和具体时间,是整个票务流程的枢纽。

CREATE TABLE screening_schedules (
    schedule_id INT AUTO_INCREMENT PRIMARY KEY,
    movie_id INT NOT NULL,
    hall_id INT NOT NULL,
    start_time DATETIME NOT NULL,
    end_time DATETIME AS (start_time + INTERVAL (SELECT duration FROM movies WHERE movie_id = screening_schedules.movie_id) MINUTE) STORED,
    price DECIMAL(10, 2) NOT NULL,
    available_seats TEXT COMMENT 'JSON格式存储座位状态,如{\"A1\":0, \"A2\":1},0可用,1已售',
    FOREIGN KEY (movie_id) REFERENCES movies(movie_id) ON DELETE CASCADE,
    FOREIGN KEY (hall_id) REFERENCES cinema_halls(hall_id) ON DELETE CASCADE,
    INDEX idx_time (start_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

此表的设计体现了对业务逻辑的深刻理解。end_time字段被设计为一个生成的列,其值通过计算start_time加上对应影片的duration自动得出,这不仅减少了数据冗余,也避免了人工录入可能带来的错误。最巧妙的是available_seats字段的设计,它采用TEXT类型存储JSON格式的字符串,动态地记录了该场次下每个座位的状态。这种设计相比为每个场次预先生成大量座位记录的方式,极大地简化了数据库结构,提高了座位状态更新的效率。同时,在start_time上建立的索引,能够高效支持用户根据时间查询场次的需求。

orders表则完整记录了每一笔交易。

CREATE TABLE orders (
    order_id VARCHAR(32) PRIMARY KEY COMMENT '使用UUID或雪花算法生成',
    user_id INT NOT NULL,
    schedule_id INT NOT NULL,
    seats JSON NOT NULL COMMENT '购买的座位列表,如[\"A1\", \"A2\"]',
    total_amount DECIMAL(10, 2) NOT NULL,
    status ENUM('Pending', 'Paid', 'Cancelled', 'Completed') DEFAULT 'Pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    paid_at TIMESTAMP NULL,
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    FOREIGN KEY (schedule_id) REFERENCES screening_schedules(schedule_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

该表使用自定义的order_id作为主键,而非自增ID,这更符合业务场景,便于分布式环境下的订单号生成。seats字段直接使用MySQL的JSON数据类型,清晰地存储了用户购买的具体座位信息,便于后续查询和统计。status字段清晰地定义了订单的生命周期,并通过paid_at等时间戳字段精确跟踪每个状态的变化时间。

在核心功能实现上,系统的Servlet控制器扮演了大脑的角色。以下是一个处理用户登录请求的Servlet核心代码片段,展示了请求处理、会话管理和业务逻辑调用的完整流程。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private UserService userService = new UserService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        try {
            User user = userService.authenticate(username, password);
            if (user != null) {
                HttpSession session = request.getSession();
                session.setAttribute("currentUser", user);
                session.setMaxInactiveInterval(30 * 60); // 会话30分钟超时
                
                if ("admin".equals(user.getRole())) {
                    response.sendRedirect("admin/dashboard.jsp");
                } else {
                    response.sendRedirect("index.jsp");
                }
            } else {
                request.setAttribute("errorMessage", "用户名或密码错误");
                request.getRequestDispatcher("login.jsp").forward(request, response);
            }
        } catch (Exception e) {
            e.printStackTrace();
            request.setAttribute("errorMessage", "系统错误,请稍后重试");
            request.getRequestDispatcher("login.jsp").forward(request, response);
        }
    }
}

这段代码清晰地展示了MVC中控制器的职责:获取参数、调用服务、根据结果控制页面跳转。它通过HttpSession管理用户登录状态,并根据用户角色进行路由分发。

用户登录注册

电影管理是管理员的核心工作之一。对应的Servlet需要处理电影信息的增删改查。以下是以新增电影为例的代码:

@WebServlet("/admin/movie/add")
public class AddMovieServlet extends HttpServlet {
    private MovieService movieService = new MovieService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        
        String title = request.getParameter("title");
        String director = request.getParameter("director");
        String actors = request.getParameter("actors");
        String plot = request.getParameter("plot");
        int duration = Integer.parseInt(request.getParameter("duration"));
        // ... 获取其他参数

        Movie movie = new Movie();
        movie.setTitle(title);
        movie.setDirector(director);
        movie.setActors(actors);
        movie.setPlotDescription(plot);
        movie.setDuration(duration);
        // ... 设置其他属性

        boolean success = movieService.addMovie(movie);
        if (success) {
            response.sendRedirect("movie-management.jsp?message=添加成功");
        } else {
            request.setAttribute("error", "添加失败,请重试");
            request.getRequestDispatcher("add-movie.jsp").forward(request, response);
        }
    }
}

此Servlet在处理POST请求时,首先设置字符编码以防止中文乱码,然后从请求中提取所有表单参数,构建Movie对象,最后调用服务层完成持久化操作。

电影管理

在线选座与购票是系统最具交互性的功能。前端的JavaScript负责座位的可视化渲染与选择,而后端的Servlet则处理座位锁定与订单生成。以下是处理选座请求的Servlet关键部分:

@WebServlet("/book/selectSeats")
public class SelectSeatsServlet extends HttpServlet {
    private BookingService bookingService = new BookingService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int scheduleId = Integer.parseInt(request.getParameter("scheduleId"));
        String[] selectedSeats = request.getParameterValues("seats[]");
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("currentUser");

        if (user == null) {
            response.sendRedirect("../login.jsp");
            return;
        }

        try {
            // 尝试锁定座位
            boolean locked = bookingService.lockSeats(scheduleId, Arrays.asList(selectedSeats));
            if (locked) {
                // 生成临时订单,跳转到支付页面
                String orderId = bookingService.createPendingOrder(user.getUserId(), scheduleId, selectedSeats);
                response.sendRedirect("confirm-payment.jsp?orderId=" + orderId);
            } else {
                request.setAttribute("error", "您选择的座位已被占用,请重新选择");
                request.getRequestDispatcher("select-seats.jsp?scheduleId=" + scheduleId).forward(request, response);
            }
        } catch (Exception e) {
            e.printStackTrace();
            request.setAttribute("error", "系统繁忙,请稍后重试");
            request.getRequestDispatcher("select-seats.jsp?scheduleId=" + scheduleId).forward(request, response);
        }
    }
}

该Servlet首先验证用户登录状态,然后调用服务层尝试锁定用户选择的座位。这是一个关键步骤,防止了并发购票时的座位冲突。锁定成功后,生成一个待支付的订单,并将用户引导至支付确认页面。

查看我的订单

服务层是业务逻辑的核心载体。以BookingService中的座位锁定方法为例,其实现需要保证操作的原子性,防止超卖。

public class BookingService {
    public boolean lockSeats(int scheduleId, List<String> seats) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            conn = DatabaseUtil.getConnection();
            conn.setAutoCommit(false); // 开启事务

            // 1. 查询当前场次的座位状态
            String sql = "SELECT available_seats FROM screening_schedules WHERE schedule_id = ? FOR UPDATE";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, scheduleId);
            rs = pstmt.executeQuery();
            
            if (!rs.next()) {
                return false;
            }
            
            String seatsJson = rs.getString("available_seats");
            JSONObject seatStatus = new JSONObject(seatsJson);
            
            // 2. 检查所选座位是否均可用
            for (String seat : seats) {
                if (seatStatus.optInt(seat, -1) != 0) { // 0表示可用
                    conn.rollback();
                    return false;
                }
            }
            
            // 3. 更新座位状态为锁定(例如状态2)
            for (String seat : seats) {
                seatStatus.put(seat, 2); // 2代表已锁定
            }
            
            // 4. 更新数据库
            sql = "UPDATE screening_schedules SET available_seats = ? WHERE schedule_id = ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, seatStatus.toString());
            pstmt.setInt(2, scheduleId);
            pstmt.executeUpdate();
            
            conn.commit(); // 提交事务
            return true;
            
        } catch (Exception e) {
            if (conn != null) {
                try { conn.rollback(); } catch (SQLException ex) {}
            }
            e.printStackTrace();
            return false;
        } finally {
            DatabaseUtil.close(conn, pstmt, rs);
        }
    }
}

这段代码展示了如何通过数据库事务和行级锁(FOR UPDATE)来实现高并发场景下的座位锁定,确保了数据的一致性。

在视图层,JSP页面通过JSTL和EL表达式动态展示数据。以下是电影列表页面的部分代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>电影列表</title>
</head>
<body>
    <div class="movie-list">
        <c:forEach var="movie" items="${movieList}">
            <div class="movie-item">
                <img src="${movie.posterUrl}" alt="${movie.title}">
                <h3>${movie.title}</h3>
                <p>导演: ${movie.director}</p>
                <p>主演: ${movie.actors}</p>
                <p>时长: ${movie.duration}分钟</p>
                <a href="movieDetail.jsp?movieId=${movie.movieId}">查看详情</a>
            </div>
        </c:forEach>
    </div>
</body>
</html>

这种写法使得JSP页面清晰简洁,将业务逻辑与页面表现彻底分离。

查看电影详情

数据库连接工具类DatabaseUtil封装了连接的获取与释放,采用了线程安全的连接池技术,提升了性能。

public class DatabaseUtil {
    private static DataSource dataSource;

    static {
        try {
            Context ctx = new InitialContext();
            dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/cinemaDB");
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }

    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();
            if (stmt != null) stmt.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

场次管理

尽管“影幕之巅”系统已经实现了核心的票务管理功能,但从产品演进和技术发展的角度看,仍有多个方向可以优化和扩展。

首先,引入分布式缓存是提升系统性能的首选方案。可以将热门电影信息、近期场次数据等读多写少的数据存入Redis等缓存中间件。例如,在MovieService中,可以先查询Redis,未命中再查询数据库,并将结果写入缓存。

public Movie getMovieById(int movieId) {
    String key = "movie:" + movieId;
    String cachedMovie = redisTemplate.opsForValue().get(key);
    if (cachedMovie != null) {
        return JSON.parseObject(cachedMovie, Movie.class);
    } else {
        Movie movie = movieDao.findById(movieId);
        if (movie != null) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(movie), 1, TimeUnit.HOURS);
        }
        return movie;
    }
}

其次,集成第三方支付平台(如支付宝、微信支付)是系统商业化的必然要求。可以设计一个支付网关Servlet,统一处理支付请求、接收异步通知和更新订单状态。关键在于处理好支付结果的异步通知和保证订单状态的最终一致性。

第三,实现真正的实时选座体验需要WebSocket技术的支持。当用户选择或释放座位时,通过WebSocket连接将座位状态变化实时推送给所有正在查看该场次的用户,避免数据不一致。这需要建立WebSocket端点来处理连接和消息广播。

第四,随着数据量的增长,简单的SQL查询可能无法满足复杂的统计分析需求。可以考虑引入Elasticsearch作为搜索和分析引擎,对电影名称、演员、简介等字段建立全文索引,提供更快速、更智能的搜索体验,并支持基于用户行为的个性化推荐。

最后,系统的微服务化改造是应对未来业务复杂度的长远之计。可以将用户服务、电影服务、订单服务、支付服务等拆分为独立的微服务,通过Spring Cloud等框架进行治理。这虽然会引入分布式系统的复杂性,但能带来更好的技术栈灵活性、独立部署和扩展能力。

该系统作为一个典型的J2EE Web应用,其架构设计、数据库建模与代码实现方式,对于理解和掌握基于Servlet容器的Web开发范式具有重要的参考价值。它清晰地展示了如何利用成熟稳定的技术组合,构建出满足实际业务需求、结构清晰且易于维护的企业级应用。

本文关键词
JSPServlet在线影院售票管理系统源码解析

上下篇

上一篇
没有更多文章
下一篇
没有更多文章