1. 项目背景与核心需求
洋州影院购票管理系统源于传统影院数字化转型的迫切需求。在移动互联网时代,观众对购票体验的要求已从简单的"能买到票"升级为"舒适便捷的个性化服务"。我们团队在实地调研中发现三个核心痛点:
- 座位信息不透明:传统购票时观众无法直观看到剩余座位分布,常出现"选座靠猜"的情况
- 高峰期排队拥堵:热门影片上映时,线下窗口平均等待时间超过25分钟
- 会员体系割裂:积分、优惠券无法跨平台使用,用户粘性低
针对这些问题,我们设计了这套基于SpringBoot2+Vue3的全栈解决方案。系统最突出的创新点是引入了动态座位可视化技术——通过WebSocket实时同步座位状态,配合Canvas绘制的3D影厅模型,用户可360度旋转查看每个座位的视角和间距。
2. 技术架构设计
2.1 前后端分离架构
采用前后端分离设计,通过RESTful API进行数据交互。这种架构的优势在于:
- 前端:Vue3组合式API开发,使用Pinia状态管理
- 后端:SpringBoot2.7 + MyBatis-Plus 3.5
- 数据库:MySQL8.0(主要利用其窗口函数和JSON特性)
mermaid复制graph TD
A[用户端] -->|HTTPS| B[Nginx]
B --> C[Vue3前端]
B --> D[SpringBoot API]
D --> E[Redis缓存]
D --> F[MySQL集群]
注意:生产环境建议将Nginx与前端静态资源部署在CDN节点,API服务部署在内网并通过Nginx反向代理暴露
2.2 高并发处理方案
针对售票高峰期的并发冲突,我们实现了三级防护:
- 第一层:Nginx限流(2000请求/秒)
- 第二层:Redis分布式锁(Redisson实现)
- 第三层:数据库乐观锁(version字段)
购票核心流程的伪代码:
java复制public Result purchaseTicket(Long userId, Long sessionId, SeatSelection seats) {
// 获取分布式锁
RLock lock = redisson.getLock("LOCK:"+sessionId);
try {
lock.lock(3, TimeUnit.SECONDS);
// 检查座位状态
List<Seat> available = seatService.checkAvailable(sessionId, seats);
if(available.size() != seats.size()) {
return Result.error("座位已被占用");
}
// 创建订单(@Transactional)
return orderService.createOrder(userId, sessionId, seats);
} finally {
lock.unlock();
}
}
3. 数据库设计精要
3.1 影片信息表优化
film_core_info表采用纵表设计解决多语言问题:
sql复制CREATE TABLE `film_i18n` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`film_uid` VARCHAR(32) NOT NULL,
`lang` CHAR(5) NOT NULL COMMENT 'zh-CN/en-US',
`title` VARCHAR(120) NOT NULL,
`description` TEXT,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_film_lang` (`film_uid`,`lang`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
3.2 座位矩阵的存储创新
传统方案用行列坐标存储座位,我们引入极坐标存储便于距离计算:
java复制public class SeatPosition {
private double radius; // 距离屏幕中心半径
private double angle; // 水平夹角
private double elevation; // 垂直高度
}
这种存储方式使得计算"黄金座位"(距离屏幕2/3处,水平视角±30°内)变得非常简单:
sql复制SELECT * FROM hall_seat_matrix
WHERE radius BETWEEN 0.6 AND 0.8
AND ABS(angle) < 30
ORDER BY elevation DESC;
4. 核心功能实现
4.1 动态选座算法
选座功能的关键在于实时性和冲突检测。我们采用的技术方案:
- 状态同步:WebSocket推送座位状态变更
- 视觉呈现:Three.js渲染3D影厅模型
- 冲突解决:前端本地维护选择状态,提交时二次校验
前端选座核心代码:
vue复制<script setup>
const selectedSeats = ref([]);
const handleSelect = (seat) => {
if(seat.status !== 'AVAILABLE') return;
// 最大选座数限制
if(selectedSeats.value.length >= 6) {
return alert('最多选择6个座位');
}
// 智能连座推荐
if(selectedSeats.value.length > 0) {
const last = selectedSeats.value[selectedSeats.value.length-1];
if(!isAdjacent(last, seat)) {
recommendAdjacentSeats(seat);
}
}
selectedSeats.value.push(seat);
};
</script>
4.2 支付系统集成
支付模块采用策略模式设计,支持多种支付方式:
java复制public interface PaymentStrategy {
PaymentResult pay(Order order);
PaymentResult refund(Order order);
}
@Service
@RequiredArgsConstructor
public class PaymentService {
private final Map<String, PaymentStrategy> strategies;
public PaymentResult handlePayment(String type, Order order) {
PaymentStrategy strategy = strategies.get(type + "Strategy");
if(strategy == null) {
throw new IllegalArgumentException("不支持的支付类型");
}
return strategy.pay(order);
}
}
已实现的支付方式包括:
- 微信支付(JSAPI、Native)
- 支付宝(网页+APP)
- 影院会员卡
- 积分抵扣
5. 性能优化实践
5.1 Elasticsearch搜索优化
影片搜索采用ES的N-gram分词:
json复制PUT /films
{
"settings": {
"analysis": {
"analyzer": {
"ngram_analyzer": {
"tokenizer": "ngram_tokenizer"
}
},
"tokenizer": {
"ngram_tokenizer": {
"type": "ngram",
"min_gram": 2,
"max_gram": 3
}
}
}
}
}
查询时使用bool组合查询:
java复制BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("title", keyword).boost(2))
.should(QueryBuilders.matchQuery("actors", keyword))
.filter(QueryBuilders.termQuery("status", "SHOWING"));
5.2 缓存策略设计
采用多级缓存架构:
- 本地缓存:Caffeine缓存影厅基础信息
- 分布式缓存:Redis缓存热门影片数据
- 浏览器缓存:ETag控制静态资源
缓存更新策略示例:
java复制@CacheEvict(value = "sessions", key = "#filmId")
public void updateFilmSessions(Long filmId) {
// 更新数据库
sessionService.refreshCache(filmId);
// 异步刷新ES
eventPublisher.publishEvent(new FilmUpdateEvent(filmId));
}
6. 安全防护措施
6.1 JWT增强方案
标准JWT方案存在注销问题,我们的改进方案:
- 短期accessToken(30分钟)
- 长期refreshToken(7天)
- Redis维护有效token白名单
令牌生成逻辑:
java复制public String generateToken(User user) {
// 生成唯一jti
String jti = UUID.randomUUID().toString();
// 存储到Redis(2小时过期)
redisTemplate.opsForValue().set(
"TOKEN:"+user.getId()+":"+jti,
"1",
2, TimeUnit.HOURS);
return Jwts.builder()
.setId(jti)
.setSubject(user.getId().toString())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
6.2 防刷单机制
针对黄牛刷票设计防护策略:
- 用户行为分析(鼠标轨迹、点击频率)
- 设备指纹识别
- 地域限制(同一IP限购5张)
实现代码片段:
java复制@Aspect
@Component
public class AntiSpamAspect {
@Around("@annotation(rateLimit)")
public Object checkRate(ProceedingJoinPoint pjp, RateLimit rateLimit) {
String key = "LIMIT:" + getClientIp() + ":" + pjp.getSignature();
Long count = redisTemplate.opsForValue().increment(key, 1);
if(count == 1) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
if(count > rateLimit.value()) {
throw new BusinessException("操作过于频繁");
}
return pjp.proceed();
}
}
7. 部署与监控
7.1 容器化部署
采用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
7.2 监控方案
使用Prometheus+Grafana监控关键指标:
- API响应时间(P99 < 500ms)
- 购票失败率(< 0.1%)
- 缓存命中率(> 90%)
SpringBoot配置示例:
properties复制management.endpoints.web.exposure.include=*
management.metrics.export.prometheus.enabled=true
management.metrics.tags.application=${spring.application.name}
8. 项目演进方向
在实际运营中,我们规划了三个演进阶段:
- 第一阶段(当前):基础购票功能
- 第二阶段:AI推荐算法(基于用户历史行为)
- 第三阶段:元宇宙影院(VR虚拟观影)
特别在选座算法上,我们正在试验基于强化学习的动态定价模型,根据实时上座率自动调整座位价格。测试数据显示,这种策略能提升15%的营收。