基于SSH框架的在线电影票务系统 - 源码深度解析

JavaJavaScriptSSH框架HTMLCSSMySQLJSP+Servlet
2026-02-254 浏览

文章摘要

本项目是一款基于经典SSH(Struts2 + Spring + Hibernate)整合框架的在线电影票务系统,旨在为影院提供一站式的数字化售票与影片管理解决方案。其核心业务价值在于彻底改变了传统线下窗口排队购票的低效模式,解决了影迷选座不直观、排片信息获取滞后以及影院后台影片与场次管理繁琐等核心...

在数字化浪潮席卷传统行业的今天,影院运营模式的革新势在必行。传统线下售票窗口前蜿蜒的长队、模糊不清的纸质座位图、以及难以实时更新的排片信息,不仅消耗了观众的耐心,也制约了影院的运营效率。为应对这些挑战,一套基于SSH(Struts2 + Spring + Hibernate)整合框架的智能影票通系统应运而生,它通过B/S架构将影院业务全面线上化,实现了从影片上架、场次排期到在线选座、电子支付的完整闭环。

该系统在技术选型上采用了经典的JavaEE三层架构,每一层都由成熟的开源框架支撑,确保了系统的高可用性、可维护性和可扩展性。表示层由Struts2框架负责,其核心控制器FilterDispatcher会拦截所有用户请求,并依据struts.xml配置文件将请求分发给对应的Action类进行处理。Action作为模型与视图的协调者,既负责接收前端表单数据,也调用业务层服务,并最终返回一个字符串结果,指引Struts2渲染特定的JSP视图。这种MVC模式的清晰分离,使得前端页面的变动不会波及后端业务逻辑。

业务逻辑层则由Spring框架的IoC(控制反转)容器统一管理。所有核心业务组件,如MovieServiceOrderService等,均以Bean的形式在applicationContext.xml中完成注册和依赖注入。Spring的AOP(面向切面编程)能力被用于声明式事务管理,通过@Transactional注解,可以轻松地为方法添加事务边界,确保例如“创建订单”与“更新座位状态”这两个数据库操作要么全部成功,要么全部回滚,保障了核心业务的数据一致性。

数据持久层是Hibernate的舞台,它实现了对象关系映射(ORM),将Java对象与数据库表无缝关联。开发者无需编写繁琐的JDBC代码和SQL语句,而是通过操作诸如MovieShowtime等实体对象,由Hibernate自动生成优化的SQL语句并执行。Hibernate提供了HQL(Hibernate Query Language)和Criteria API两种强大的数据查询方式,前者类似于面向对象的SQL,后者则提供了类型安全的编程式查询接口,极大地提升了开发效率和代码的可读性。

系统架构设计

数据库设计是系统稳定运行的基石,其核心表结构的设计直接关系到业务逻辑的复杂度和数据完整性。本系统共设计了9张核心数据表,以下是其中几个关键表的设计分析:

1. 电影信息表 (movie): 此表是系统的内容核心,存储了所有影片的元数据。其设计不仅包含了片名、导演、演员、类型、时长、简介等基本信息,还考虑了运营需求,如is_hot字段用于标识热门影片,在首页进行推荐;poster_url字段存储电影海报的网络路径,实现内容的可视化展示。release_date(上映日期)是关键的业务字段,前台系统可据此筛选正在热映和即将上映的影片。

CREATE TABLE movie (
  movie_id int(11) NOT NULL AUTO_INCREMENT,
  movie_name_cn varchar(50) DEFAULT NULL,
  movie_name_en varchar(50) DEFAULT NULL,
  director varchar(20) DEFAULT NULL,
  starring varchar(100) DEFAULT NULL,
  movie_type varchar(20) DEFAULT NULL,
  duration int(11) DEFAULT NULL,
  country varchar(20) DEFAULT NULL,
  language varchar(20) DEFAULT NULL,
  description text,
  poster_url varchar(200) DEFAULT NULL,
  is_hot int(11) DEFAULT '0',
  release_date date DEFAULT NULL,
  PRIMARY KEY (movie_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. 放映场次表 (showtime): 该表是连接电影、影厅与座位的枢纽,是业务逻辑最复杂的实体之一。除了基本的movie_id(电影ID)和cinema_hall_id(影厅ID)外键,start_timeend_time字段定义了场次的精确时间窗口,是用户筛选和购票的基础。price字段支持动态定价策略。一个精妙的设计在于,通过start_timecinema_hall_id可以建立唯一约束,有效防止了同一影厅在不同场次的时间重叠,这是排片管理的关键约束。

CREATE TABLE showtime (
  showtime_id int(11) NOT NULL AUTO_INCREMENT,
  movie_id int(11) DEFAULT NULL,
  cinema_hall_id int(11) DEFAULT NULL,
  start_time datetime DEFAULT NULL,
  end_time datetime DEFAULT NULL,
  price decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (showtime_id),
  KEY movie_id (movie_id),
  KEY cinema_hall_id (cinema_hall_id),
  CONSTRAINT showtime_ibfk_1 FOREIGN KEY (movie_id) REFERENCES movie (movie_id),
  CONSTRAINT showtime_ibfk_2 FOREIGN KEY (cinema_hall_id) REFERENCES cinema_hall (cinema_hall_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3. 订单表 (orders): 此表记录了所有交易行为,是系统的财务核心。order_number字段采用时间戳或特定算法生成的唯一序列号,作为订单的唯一标识。total_amount为订单总金额。status字段使用枚举或整数代表订单状态(如:待支付、已支付、已取消、已完成),是驱动订单流程状态机的关键。user_id关联用户,而showtime_idseat_ids(可能以JSON字符串或关联表形式存在)则精确锁定了用户购买的票务资源。这种设计确保了在并发购票场景下,通过数据库事务和乐观锁机制,避免“一票多卖”的情况。

订单管理界面

系统的实体模型(Entity)是Hibernate ORM的直接体现,每个实体类对应数据库中的一张表。以Movie实体为例,其Java类的定义不仅包含了与表字段一一对应的属性,还通过注解定义了映射关系,并包含了相关的业务逻辑方法。

@Entity
@Table(name = "movie")
public class Movie implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "movie_id")
    private Integer movieId;

    @Column(name = "movie_name_cn")
    private String movieNameCn;

    @Column(name = "movie_name_en")
    private String movieNameEn;

    @Column(name = "director")
    private String director;

    // ... 其他字段的注解 ...

    // 定义与Showtime的一对多关系,由Showtime表中的movie_id维护外键关系。
    // CascadeType.ALL表示对Movie的操作会级联影响到其关联的Showtime。
    // 延迟加载(LAZY)为提高性能,在访问showtimes集合时才从数据库加载数据。
    @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private Set<Showtime> showtimes = new HashSet<>();

    // Getter and Setter 方法
    public Integer getMovieId() { return movieId; }
    public void setMovieId(Integer movieId) { this.movieId = movieId; }

    public String getMovieNameCn() { return movieNameCn; }
    public void setMovieNameCn(String movieNameCn) { this.movieNameCn = movieNameCn; }

    // ... 其他Getter和Setter ...

    public Set<Showtime> getShowtimes() { return showtimes; }
    public void setShowtimes(Set<Showtime> showtimes) { this.showtimes = showtimes; }
}

在核心功能实现上,系统的业务逻辑层(Service Layer)负责处理复杂的业务规则。以创建订单为例,OrderServicecreateOrder方法需要在一个事务内完成多个步骤,包括校验座位可用性、计算总价、生成订单、锁定座位等。

@Service("orderService")
@Transactional // 声明此Service中的所有方法都在事务中运行
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderDao orderDao;
    @Autowired
    private ShowtimeDao showtimeDao;
    @Autowired
    private SeatDao seatDao;

    @Override
    public Order createOrder(Integer userId, Integer showtimeId, List<Integer> seatIds) throws BusinessException {
        // 1. 根据showtimeId查询场次信息,并校验其有效性(例如,是否已过期)
        Showtime showtime = showtimeDao.findById(showtimeId);
        if (showtime == null) {
            throw new BusinessException("指定的场次不存在");
        }
        if (showtime.getStartTime().before(new Date())) {
            throw new BusinessException("该场次已开始,无法购票");
        }

        // 2. 检查所选座位在当前场次下是否可用
        for (Integer seatId : seatIds) {
            Seat seat = seatDao.findById(seatId);
            // 假设Seat实体有一个方法或状态字段来检查是否被该场次占用
            if (!seat.isAvailableForShowtime(showtimeId)) {
                throw new BusinessException("座位ID为 " + seatId + " 的座位已被占用");
            }
        }

        // 3. 计算订单总金额:票价 * 座位数量
        BigDecimal unitPrice = showtime.getPrice();
        BigDecimal totalAmount = unitPrice.multiply(new BigDecimal(seatIds.size()));

        // 4. 生成订单实体并保存
        Order order = new Order();
        order.setOrderNumber(generateOrderNumber()); // 生成唯一订单号
        order.setUser(new User(userId)); // 设置用户(假设通过ID构建一个代理对象)
        order.setShowtime(showtime);
        order.setSeatIds(convertSeatIdsToString(seatIds)); // 将座位ID列表转换为字符串存储
        order.setTotalAmount(totalAmount);
        order.setStatus(OrderStatus.PENDING_PAYMENT); // 初始状态为待支付
        order.setCreateTime(new Date());

        orderDao.save(order);

        // 5. 更新座位状态为已锁定(或创建座位与场次的关联记录)
        for (Integer seatId : seatIds) {
            seatDao.lockSeatForShowtime(seatId, showtimeId, order.getOrderId());
        }

        return order;
    }

    // 生成订单号的辅助方法
    private String generateOrderNumber() {
        return "ORD" + System.currentTimeMillis() + (int)(Math.random() * 1000);
    }
}

表示层的交互由Struts2的Action类处理。用户登录Action负责验证用户凭证,并跳转到相应页面。

public class UserAction extends ActionSupport {
    private String username;
    private String password;
    private User user; // 用于在成功登录后向页面传递用户信息
    private UserService userService; // 由Spring注入

    // Struts2执行的方法
    public String login() {
        try {
            user = userService.validateLogin(username, password);
            if (user != null) {
                // 将用户信息存入Session
                ActionContext.getContext().getSession().put("currentUser", user);
                return SUCCESS;
            } else {
                addActionError("用户名或密码错误!");
                return INPUT;
            }
        } catch (Exception e) {
            addActionError("登录过程发生错误:" + e.getMessage());
            return ERROR;
        }
    }

    // Getter and Setter 方法,Struts2通过这些方法注入请求参数
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    public User getUser() { return user; }
    public void setUser(User user) { this.user = user; }
    // UserService的setter方法,供Spring依赖注入
    public void setUserService(UserService userService) { this.userService = userService; }
}

对应的struts.xml配置文件将HTTP请求映射到Action的方法,并指定不同的结果视图。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
    <package name="default" extends="struts-default" namespace="/">
        <!-- 用户登录Action配置 -->
        <action name="userLogin" class="userAction" method="login">
            <!-- 结果为success时,跳转到首页 -->
            <result name="success" type="redirectAction">index</result>
            <!-- 结果为input或error时,返回登录页面并显示错误信息 -->
            <result name="input">/WEB-INF/pages/user/login.jsp</result>
            <result name="error">/WEB-INF/pages/user/login.jsp</result>
        </action>

        <!-- 首页Action -->
        <action name="index" class="com.maancode.action.IndexAction">
            <result>/WEB-INF/pages/common/index.jsp</result>
        </action>
    </package>
</struts>

数据访问层(DAO)使用Hibernate的HibernateTemplateSession进行数据操作,以下是一个基础的DAO实现示例。

@Repository("movieDao")
public class MovieDaoImpl extends HibernateDaoSupport implements MovieDao {

    // 使用@Autowired注解为HibernateDaoSupport注入SessionFactory
    @Autowired
    public void setMySessionFactory(SessionFactory sessionFactory){
        super.setSessionFactory(sessionFactory);
    }

    @Override
    public Movie findById(Integer id) {
        return getHibernateTemplate().get(Movie.class, id);
    }

    @Override
    public List<Movie> findAllHotMovies() {
        String hql = "FROM Movie m WHERE m.isHot = 1 ORDER BY m.releaseDate DESC";
        return (List<Movie>) getHibernateTemplate().find(hql);
    }

    @Override
    public void save(Movie movie) {
        getHibernateTemplate().saveOrUpdate(movie);
    }

    @Override
    public void delete(Movie movie) {
        getHibernateTemplate().delete(movie);
    }
}

电影管理后台 用户选座界面

尽管当前的智能影票通系统已经实现了核心业务功能,但在未来仍有广阔的优化和扩展空间。

  1. 引入Redis缓存:将热门电影列表、影院信息、短期内不会变动的场次信息等高频读取但更新不频繁的数据存入Redis。这可以极大减轻MySQL数据库的读取压力,提升系统响应速度。例如,在MovieService中,可以先从Redis查询热门电影,若不存在,再从数据库查询并写入Redis。

  2. 集成第三方支付与实名认证:当前系统可模拟支付流程。未来可集成支付宝、微信支付等主流支付网关的SDK,实现真正的线上支付。对于政策要求,可接入身份证实名认证API,在购票时完成观影人实名信息校验。

  3. 实现分布式会话与集群部署:当单台服务器无法承载流量时,系统需扩展为集群。此时,用户的Session信息不能存储在单个服务器内存中,可以引入Spring Session等项目,将Session存储到Redis等集中式缓存中,实现分布式会话管理。

  4. 构建微服务架构:将单体应用拆分为电影服务、订单服务、用户服务、支付服务等独立的微服务。每个服务专注自己的业务领域,通过RESTful API或RPC进行通信。这有助于团队并行开发、技术选型更灵活、服务独立扩缩容。例如,订单服务在购票高峰期可以单独扩容。

  5. 增强数据分析与推荐功能:在数据积累的基础上,可以引入大数据分析平台(如ELK栈或Spark),分析用户的购票偏好、观影习惯,构建推荐算法模型。在用户首页为其个性化推荐可能感兴趣的电影,实现精准营销,提升转化率。

这套系统的价值不仅在于其功能本身,更在于其作为经典SSH框架的完整实践,清晰地展示了如何将表示层、业务层、持久层解耦,如何通过ORM管理数据关系,以及如何通过声明式事务保证业务一致性。它为理解企业级Java应用开发提供了绝佳的范本,也为后续的技术演进奠定了坚实的基础。

本文关键词
SSH框架在线电影票务系统Struts2SpringHibernate

上下篇

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