1. 剧场管理系统架构设计
这个基于SpringBoot+Vue的剧场管理系统采用了典型的前后端分离架构。后端使用SpringBoot框架构建RESTful API,前端通过Vue.js实现用户界面,两者通过HTTP协议进行数据交互。
后端技术栈选择SpringBoot的原因主要有三点:
- 自动配置特性大幅简化了Spring应用的初始搭建和开发过程
- 内嵌Tomcat服务器,无需额外部署WAR文件
- 丰富的starter依赖可以快速集成各种常用组件(如MyBatis、Redis等)
前端选择Vue.js的考虑在于:
- 渐进式框架特性,可以按需引入功能
- 组件化开发模式非常适合管理系统这类多视图应用
- 响应式数据绑定简化了DOM操作
- 丰富的生态系统(Vue Router、Vuex等)
数据库选用MySQL主要基于以下因素:
- 开源免费,社区支持完善
- 事务支持完善,适合票务类业务场景
- 性能满足中小型剧场管理需求
- 与SpringBoot生态集成良好
提示:实际开发中建议使用MySQL 5.7+版本,避免使用8.0某些新特性可能导致的兼容性问题
2. 开发环境搭建与配置
2.1 后端环境准备
后端开发需要配置以下环境:
- JDK 1.8(推荐Oracle JDK或OpenJDK)
- Maven 3.6+(依赖管理)
- IntelliJ IDEA(开发IDE)
- MySQL 5.7+(数据库)
关键Maven依赖包括:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
application.yml典型配置示例:
yaml复制server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/theater_db?useSSL=false&serverTimezone=UTC
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.theater.entity
2.2 前端环境配置
前端开发需要:
- Node.js 14+
- Vue CLI 4.5+
- VS Code(推荐编辑器)
创建Vue项目命令:
bash复制npm install -g @vue/cli
vue create theater-frontend
cd theater-frontend
npm install axios vue-router vuex --save
关键前端依赖版本建议:
- vue: ^2.6.11
- vue-router: ^3.2.0
- vuex: ^3.4.0
- axios: ^0.21.1
- element-ui: ^2.15.1(如使用UI框架)
3. 数据库设计与实现
3.1 核心表结构设计
剧场管理系统主要包含以下表:
- 用户表(users)
sql复制CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`real_name` varchar(50) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`role` enum('admin','staff','customer') NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 演出表(performances)
sql复制CREATE TABLE `performances` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`description` text,
`duration` int(11) NOT NULL COMMENT '分钟',
`poster_url` varchar(255) DEFAULT NULL,
`status` enum('upcoming','ongoing','finished') NOT NULL,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 场次表(shows)
sql复制CREATE TABLE `shows` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`performance_id` int(11) NOT NULL,
`hall_id` int(11) NOT NULL,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
`price` decimal(10,2) NOT NULL,
`available_seats` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `performance_id` (`performance_id`),
KEY `hall_id` (`hall_id`),
CONSTRAINT `shows_ibfk_1` FOREIGN KEY (`performance_id`) REFERENCES `performances` (`id`),
CONSTRAINT `shows_ibfk_2` FOREIGN KEY (`hall_id`) REFERENCES `halls` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 订单表(orders)
sql复制CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`show_id` int(11) NOT NULL,
`order_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`total_amount` decimal(10,2) NOT NULL,
`status` enum('pending','paid','cancelled','refunded') NOT NULL,
`seat_count` int(11) NOT NULL,
`seat_numbers` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `show_id` (`show_id`),
CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
CONSTRAINT `orders_ibfk_2` FOREIGN KEY (`show_id`) REFERENCES `shows` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 数据库优化建议
-
为频繁查询的字段添加索引,如:
sql复制ALTER TABLE `shows` ADD INDEX `idx_time` (`start_time`, `end_time`); -
对于大文本字段(如演出描述),考虑使用垂直分表
-
定期执行ANALYZE TABLE更新统计信息
-
配置合理的连接池参数(如HikariCP):
yaml复制spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 idle-timeout: 30000 max-lifetime: 1800000
4. 核心功能模块实现
4.1 用户认证与授权
采用JWT实现无状态认证:
- 添加JWT依赖:
xml复制<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
- JWT工具类示例:
java复制public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key";
private static final long EXPIRATION_TIME = 86400000; // 24小时
public static String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.claim("roles", userDetails.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public static String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// 其他验证方法...
}
- Spring Security配置:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/staff/**").hasAnyRole("STAFF", "ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
// 其他配置...
}
4.2 演出管理模块
- 演出实体类:
java复制@Data
public class Performance {
private Integer id;
private String title;
private String description;
private Integer duration;
private String posterUrl;
private PerformanceStatus status;
private LocalDateTime startTime;
private LocalDateTime endTime;
private List<Show> shows;
}
public enum PerformanceStatus {
UPCOMING, ONGOING, FINISHED
}
- 演出服务层:
java复制@Service
public class PerformanceService {
@Autowired
private PerformanceMapper performanceMapper;
public PageInfo<Performance> getPerformances(int pageNum, int pageSize,
String keyword, PerformanceStatus status) {
PageHelper.startPage(pageNum, pageSize);
List<Performance> performances = performanceMapper.selectByCondition(keyword, status);
return new PageInfo<>(performances);
}
public Performance getPerformanceWithShows(Integer id) {
Performance performance = performanceMapper.selectById(id);
if (performance != null) {
performance.setShows(showMapper.selectByPerformanceId(id));
}
return performance;
}
// 其他业务方法...
}
- 演出控制器:
java复制@RestController
@RequestMapping("/api/performances")
public class PerformanceController {
@Autowired
private PerformanceService performanceService;
@GetMapping
public Result list(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) PerformanceStatus status) {
PageInfo<Performance> pageInfo = performanceService.getPerformances(
pageNum, pageSize, keyword, status);
return Result.success(pageInfo);
}
@GetMapping("/{id}")
public Result detail(@PathVariable Integer id) {
Performance performance = performanceService.getPerformanceWithShows(id);
return Result.success(performance);
}
// 其他API方法...
}
4.3 票务管理模块
- 订单创建逻辑:
java复制@Transactional
public Order createOrder(Integer userId, Integer showId, List<String> seatNumbers) {
// 1. 验证场次和座位可用性
Show show = showMapper.selectById(showId);
if (show == null || show.getAvailableSeats() < seatNumbers.size()) {
throw new BusinessException("场次不存在或座位不足");
}
// 2. 锁定座位(实际项目中可能需要更复杂的座位锁定机制)
if (!seatService.checkSeatAvailable(showId, seatNumbers)) {
throw new BusinessException("部分座位已被占用");
}
// 3. 创建订单
Order order = new Order();
order.setUserId(userId);
order.setShowId(showId);
order.setSeatCount(seatNumbers.size());
order.setSeatNumbers(String.join(",", seatNumbers));
order.setTotalAmount(show.getPrice().multiply(BigDecimal.valueOf(seatNumbers.size())));
order.setStatus(OrderStatus.PENDING);
orderMapper.insert(order);
// 4. 更新场次可用座位数
show.setAvailableSeats(show.getAvailableSeats() - seatNumbers.size());
showMapper.updateById(show);
return order;
}
- 支付处理逻辑:
java复制@Transactional
public void processPayment(Integer orderId, String paymentMethod) {
Order order = orderMapper.selectById(orderId);
if (order == null || !order.getStatus().equals(OrderStatus.PENDING)) {
throw new BusinessException("订单无效或已处理");
}
// 模拟支付处理
boolean paymentSuccess = paymentService.process(order.getTotalAmount(), paymentMethod);
if (paymentSuccess) {
order.setStatus(OrderStatus.PAID);
order.setPaymentTime(LocalDateTime.now());
orderMapper.updateById(order);
// 发送票据(邮件/短信)
ticketService.generateAndSendTicket(order);
} else {
// 支付失败,释放座位
Show show = showMapper.selectById(order.getShowId());
show.setAvailableSeats(show.getAvailableSeats() + order.getSeatCount());
showMapper.updateById(show);
throw new BusinessException("支付处理失败");
}
}
5. 前端实现关键点
5.1 Vue项目结构
典型项目目录结构:
code复制src/
├── api/ # API请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数
├── views/ # 页面组件
│ ├── admin/ # 管理后台页面
│ ├── auth/ # 认证相关页面
│ ├── performance/ # 演出相关页面
│ └── user/ # 用户中心页面
├── App.vue # 根组件
└── main.js # 应用入口
5.2 演出列表页实现
- Vue组件示例:
vue复制<template>
<div class="performance-list">
<el-row :gutter="20">
<el-col :span="6" v-for="item in performances" :key="item.id">
<el-card :body-style="{ padding: '0px' }">
<img :src="item.posterUrl" class="performance-image">
<div style="padding: 14px;">
<h3>{{ item.title }}</h3>
<div class="performance-meta">
<span>{{ formatDate(item.startTime) }}</span>
<span>{{ item.duration }}分钟</span>
</div>
<div class="bottom">
<el-button type="text" @click="viewDetail(item.id)">查看详情</el-button>
<el-tag :type="getStatusType(item.status)">
{{ getStatusText(item.status) }}
</el-tag>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-pagination
@current-change="handlePageChange"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
layout="prev, pager, next">
</el-pagination>
</div>
</template>
<script>
import { getPerformances } from '@/api/performance'
export default {
data() {
return {
performances: [],
pagination: {
current: 1,
size: 12,
total: 0
}
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
const { data } = await getPerformances({
pageNum: this.pagination.current,
pageSize: this.pagination.size
})
this.performances = data.list
this.pagination.total = data.total
},
handlePageChange(page) {
this.pagination.current = page
this.fetchData()
},
viewDetail(id) {
this.$router.push(`/performance/${id}`)
},
// 其他方法...
}
}
</script>
- API封装示例:
javascript复制// src/api/performance.js
import request from '@/utils/request'
export function getPerformances(params) {
return request({
url: '/api/performances',
method: 'get',
params
})
}
export function getPerformanceDetail(id) {
return request({
url: `/api/performances/${id}`,
method: 'get'
})
}
// 其他API方法...
5.3 状态管理设计
使用Vuex管理全局状态:
javascript复制// src/store/modules/user.js
const state = {
token: localStorage.getItem('token') || '',
userInfo: JSON.parse(localStorage.getItem('userInfo')) || null
}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
localStorage.setItem('token', token)
},
SET_USER_INFO: (state, userInfo) => {
state.userInfo = userInfo
localStorage.setItem('userInfo', JSON.stringify(userInfo))
},
CLEAR_AUTH: (state) => {
state.token = ''
state.userInfo = null
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
}
}
const actions = {
login({ commit }, { username, password }) {
return new Promise((resolve, reject) => {
login({ username, password }).then(response => {
const { token, user } = response.data
commit('SET_TOKEN', token)
commit('SET_USER_INFO', user)
resolve()
}).catch(error => {
reject(error)
})
})
},
logout({ commit }) {
return new Promise(resolve => {
commit('CLEAR_AUTH')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
6. 系统部署与运维
6.1 后端部署方案
- 打包SpringBoot应用:
bash复制mvn clean package -DskipTests
- 使用Docker部署:
dockerfile复制# Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/theater-backend-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
- 构建并运行容器:
bash复制docker build -t theater-backend .
docker run -d -p 8080:8080 --name theater-backend theater-backend
6.2 前端部署方案
- 构建生产环境代码:
bash复制npm run build
- Nginx配置示例:
nginx复制server {
listen 80;
server_name your-domain.com;
location / {
root /path/to/theater-frontend/dist;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6.3 性能优化建议
- 后端优化:
- 启用SpringBoot Actuator监控端点
- 配置合理的JVM参数(-Xms, -Xmx)
- 使用Redis缓存热点数据
- 对复杂查询添加数据库索引
- 前端优化:
- 配置Gzip压缩
- 使用CDN分发静态资源
- 按需加载路由组件
- 启用HTTP/2
- 数据库优化:
- 配置合理的缓冲池大小
- 定期优化表
- 考虑读写分离
7. 常见问题与解决方案
7.1 跨域问题处理
SpringBoot后端配置CORS:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.maxAge(3600);
}
}
7.2 文件上传问题
- SpringBoot文件上传配置:
java复制@RestController
@RequestMapping("/api/upload")
public class UploadController {
@Value("${file.upload-dir}")
private String uploadDir;
@PostMapping
public Result upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.fail("请选择文件");
}
try {
String fileName = UUID.randomUUID() + "." +
FilenameUtils.getExtension(file.getOriginalFilename());
Path path = Paths.get(uploadDir, fileName);
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return Result.success("/uploads/" + fileName);
} catch (IOException e) {
return Result.fail("上传失败");
}
}
}
- 前端上传组件示例:
vue复制<template>
<el-upload
action="/api/upload"
:on-success="handleSuccess"
:before-upload="beforeUpload">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2MB</div>
</el-upload>
</template>
<script>
export default {
methods: {
beforeUpload(file) {
const isImage = ['image/jpeg', 'image/png'].includes(file.type)
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('只能上传图片文件!')
}
if (!isLt2M) {
this.$message.error('图片大小不能超过2MB!')
}
return isImage && isLt2M
},
handleSuccess(response) {
if (response.code === 200) {
this.$emit('uploaded', response.data)
}
}
}
}
</script>
7.3 并发订票问题
使用数据库乐观锁处理并发:
java复制@Transactional
public Order createOrderWithLock(Integer userId, Integer showId, List<String> seatNumbers) {
// 1. 查询场次并加锁
Show show = showMapper.selectByIdForUpdate(showId);
if (show.getAvailableSeats() < seatNumbers.size()) {
throw new BusinessException("座位不足");
}
// 2. 检查座位可用性
if (!seatService.checkSeatAvailableWithLock(showId, seatNumbers)) {
throw new BusinessException("部分座位已被占用");
}
// 3. 创建订单(同前)
// ...
// 4. 更新场次可用座位数(带版本检查)
int rows = showMapper.updateSeatsWithVersion(
showId,
show.getAvailableSeats() - seatNumbers.size(),
show.getVersion());
if (rows == 0) {
throw new ConcurrentModificationException("订票冲突,请重试");
}
return order;
}
对应的Mapper方法:
xml复制<update id="updateSeatsWithVersion">
UPDATE shows
SET available_seats = #{availableSeats},
version = version + 1
WHERE id = #{id} AND version = #{version}
</update>
8. 项目扩展与优化方向
- 微服务化改造:
- 将系统拆分为用户服务、演出服务、订单服务等独立微服务
- 使用Spring Cloud Alibaba实现服务治理
- 引入分布式事务解决方案(如Seata)
- 引入消息队列:
- 使用RabbitMQ处理异步通知(如订票成功短信)
- 实现订单超时自动取消功能
- 增强安全措施:
- 实现接口防刷限流
- 敏感数据加密存储
- 完善操作日志审计
- 性能监控:
- 集成Prometheus + Grafana监控系统
- 实现业务指标监控(如订票成功率、响应时间等)
- 移动端适配:
- 开发微信小程序版本
- 实现PWA渐进式Web应用
- 数据分析:
- 集成ELK日志分析系统
- 实现用户行为分析
- 构建演出推荐系统
提示:在实际项目迭代中,建议先实现核心功能,再根据业务需求逐步扩展。过早优化可能导致开发效率降低
