1. 项目概述与核心技术栈选型
这个前后端分离的电影订票及评论系统采用了当前主流的企业级开发技术组合。前端使用Vue.js框架构建响应式用户界面,后端基于SpringBoot快速搭建RESTful API服务,数据持久层采用MyBatis操作MySQL数据库。这种架构设计充分体现了现代Web开发中关注点分离的理念。
为什么选择这个技术组合?从实际项目经验来看:
- SpringBoot的自动配置特性让后端服务搭建变得极其高效,内置Tomcat容器省去了传统Servlet容器的繁琐配置
- Vue的组件化开发模式与响应式数据绑定特别适合构建动态交互丰富的电影展示和订票界面
- MyBatis在复杂业务查询场景下比JPA/Hibernate更灵活,特别是电影排片、座位锁定这类需要精细控制SQL的场景
- MySQL作为关系型数据库在事务一致性方面表现优异,这对保证订票业务的ACID特性至关重要
提示:在真实线上环境部署时,建议将MySQL替换为云数据库服务以获得更好的可用性保障,本地开发环境用社区版即可
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计与模块划分
2.1 前后端分离架构解析
系统采用典型的前后端分离架构,前端Vue应用通过axios发送HTTP请求到后端SpringBoot接口。这种架构的核心优势在于:
- 开发效率提升:前后端可以并行开发,只需约定好API接口规范
- 性能优化空间:前端可以做资源打包、CDN分发等优化
- 技术栈灵活性:未来可以单独替换前端或后端技术栈而不影响整体系统
在电影订票场景中,我们特别设计了以下API端点:
/api/movies获取电影列表/api/schedules查询排片信息/api/seats座位状态查询与锁定/api/orders订单创建与支付/api/comments影评发布与获取
2.2 数据库ER设计关键点
MySQL数据库设计主要包含以下核心表:
sql复制CREATE TABLE `movie` (
`id` int NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`cover_url` varchar(255) DEFAULT NULL,
`duration` int DEFAULT NULL COMMENT '分钟',
`release_date` date DEFAULT NULL,
`score` decimal(3,1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `schedule` (
`id` int NOT NULL AUTO_INCREMENT,
`movie_id` int NOT NULL,
`cinema_hall` varchar(50) NOT NULL,
`start_time` datetime NOT NULL,
`price` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`movie_id`) REFERENCES `movie` (`id`)
);
注意:实际项目中建议为排片表添加日期范围索引,因为这是高频查询条件
3. 核心功能实现细节
3.1 电影座位锁定机制
订票系统最关键的并发控制在于座位锁定。我们采用乐观锁+状态标记的方案:
- 前端展示座位时查询
seat_status表获取实时状态 - 用户选择座位后,前端发送锁定请求包含版本号
- 后端校验版本号并执行更新:
java复制@Transactional
public boolean lockSeats(List<Integer> seatIds, Long version) {
// 检查版本号是否匹配
int affectedRows = seatMapper.updateLockStatus(
seatIds,
SeatStatus.AVAILABLE,
SeatStatus.LOCKED,
version
);
return affectedRows == seatIds.size();
}
3.2 支付超时自动释放
为避免座位被长期锁定不支付,系统配置了定时任务:
java复制@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void releaseExpiredLocks() {
List<SeatLock> expiredLocks = seatLockMapper.findExpiredLocks(
LocalDateTime.now().minusMinutes(15));
expiredLocks.forEach(lock -> {
seatMapper.updateStatusByIds(
lock.getSeatIds(),
SeatStatus.AVAILABLE
);
seatLockMapper.delete(lock.getId());
});
}
4. 前端关键技术实现
4.1 影院座位可视化组件
使用SVG动态渲染影院座位图,关键代码:
vue复制<template>
<svg :width="hallWidth" :height="hallHeight">
<g v-for="(row, rowIndex) in seatLayout" :key="rowIndex">
<rect
v-for="seat in row"
:key="seat.id"
:x="seat.x"
:y="seat.y"
:fill="getSeatColor(seat.status)"
@click="handleSeatClick(seat)"
/>
</g>
</svg>
</template>
<script>
export default {
methods: {
getSeatColor(status) {
return {
[SeatStatus.AVAILABLE]: '#4CAF50',
[SeatStatus.LOCKED]: '#FFC107',
[SeatStatus.SOLD]: '#F44336'
}[status];
}
}
}
</script>
4.2 影评无限滚动加载
优化长影评列表的浏览体验:
javascript复制export default {
data() {
return {
isLoading: false,
pageSize: 10,
currentPage: 1,
comments: []
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll);
this.loadComments();
},
methods: {
async loadComments() {
if(this.isLoading) return;
this.isLoading = true;
const res = await api.getComments({
movieId: this.movieId,
page: this.currentPage,
size: this.pageSize
});
this.comments = [...this.comments, ...res.data];
this.currentPage++;
this.isLoading = false;
},
handleScroll() {
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
if(scrollTop + clientHeight >= scrollHeight - 100) {
this.loadComments();
}
}
}
}
5. 系统部署实战指南
5.1 后端SpringBoot打包与运行
- 使用Maven打包可执行JAR:
bash复制mvn clean package -DskipTests
- 生产环境推荐使用Docker容器化部署:
dockerfile复制FROM openjdk:11-jre
COPY target/movie-ticket-*.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
- 通过环境变量配置数据库连接:
properties复制spring.datasource.url=jdbc:mysql://${DB_HOST}:3306/movie_db
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASS}
5.2 前端Vue项目部署
- 生产环境构建:
bash复制npm run build
- Nginx配置示例:
nginx复制server {
listen 80;
server_name movie.example.com;
location / {
root /var/www/movie-ticket/dist;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
}
}
6. 常见问题排查与优化建议
6.1 高并发订票场景优化
当遇到热门电影开售时,系统需要应对突发流量:
-
数据库层面:
- 为
schedule和seat_status表添加合适索引 - 考虑使用Redis缓存热门电影数据
- 为
-
应用层面:
- 引入分布式锁控制座位操作
- 采用消息队列削峰填谷
java复制// Redisson分布式锁示例
public boolean lockSeatsWithDistributedLock(List<Integer> seatIds) {
RLock lock = redissonClient.getLock("seat_lock:" + seatIds.hashCode());
try {
boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS);
if(acquired) {
// 执行座位锁定逻辑
}
} finally {
lock.unlock();
}
}
6.2 跨域问题解决方案
开发阶段常见的前后端联调问题:
- 后端配置CORS(推荐生产环境使用):
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("*")
.allowCredentials(true);
}
}
- 前端开发环境代理配置(vue.config.js):
javascript复制module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
7. 项目扩展方向建议
基于现有系统可以进一步扩展的功能点:
-
推荐算法集成:
- 基于用户历史订票记录实现协同过滤推荐
- 使用Spring的@Async实现异步计算推荐结果
-
微信小程序端:
- 复用现有后端API
- 使用Uniapp框架快速构建跨平台小程序
-
数据分析看板:
- 使用ECharts可视化电影票房数据
- 定时任务统计每日/周/月销售报表
java复制// 简单的票房统计示例
public List<BoxOfficeVO> getDailyBoxOffice(LocalDate date) {
return orderMapper.selectBoxOfficeByDate(date)
.stream()
.collect(Collectors.groupingBy(
BoxOfficeDTO::getMovieId,
Collectors.summingDouble(BoxOfficeDTO::getAmount)
))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(entry -> new BoxOfficeVO(
movieService.getById(entry.getKey()).getTitle(),
entry.getValue()
))
.collect(Collectors.toList());
}
