前言
💖💖作者:计算机程序员小杨 💙💙个人简介:我是一名计算机相关专业的从业者,擅长Java、微信小程序、Python、Golang、安卓Android等多个IT方向。会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。热爱技术,喜欢钻研新工具和框架,也乐于通过代码解决实际问题,大家有技术代码这一块的问题可以问我! 💛💛想说的话:感谢大家的关注与支持! 💕💕文末获取源码联系 计算机程序员小杨 💜💜 网站实战项目 安卓/小程序实战项目 大数据实战项目 深度学习实战项目 计算机毕业设计选题 💜💜
一.开发工具简介
开发语言:Java+Python(两个版本都支持) 后端框架:Spring Boot(Spring+SpringMVC+Mybatis)+Django(两个版本都支持) 前端:Vue+ElementUI+HTML 数据库:MySQL 系统架构:B/S 开发工具:IDEA(Java的)或者PyCharm(Python的)
二.系统内容简介
分布式演唱会抢票系统是一款基于Spring Boot框架开发的在线票务预订平台,采用Vue前端技术与MySQL数据库构建,旨在解决演唱会票务销售中的高并发抢票问题。系统通过演唱会类型管理模块对不同风格的演唱会进行分类,包括流行音乐、摇滚、民谣、说唱等类型,方便用户按照兴趣筛选场次。演唱会票务管理模块负责票务信息的发布与维护,包括演出时间、地点、座位分区、票价设置、库存数量等关键信息,管理员可以实时调整票务状态和库存配置。系统的核心在于抢票机制的设计,通过数据库锁机制、库存预扣、订单超时释放等技术手段应对高并发场景下的超卖问题,确保在大量用户同时抢票时系统能够稳定运行并保证数据一致性。用户通过个人中心可以查看自己的抢票历史、订单状态、个人信息等内容,订单管理模块记录了从下单、支付、出票到退款的完整流程,用户可以在规定时间内完成支付,超时未付款的订单会自动取消并释放库存供其他用户购买。用户管理功能为管理员提供了对平台用户的统一管理能力,包括用户注册审核、账号状态管理、购票记录查询等,系统管理模块则涵盖了系统参数配置、日志记录、数据统计等后台管理功能,整体构成了一个集票务发布、在线抢票、订单处理、用户管理于一体的综合性票务销售平台。
三.系统功能演示
四.系统界面展示
五.系统源码展示
/**
* 演唱会票务管理 - 抢票核心逻辑
* 处理用户抢票请求,通过数据库锁机制防止超卖,确保高并发场景下的数据一致性
*/
@PostMapping("/grabTicket")
@Transactional(rollbackFor = Exception.class)
public Result grabTicket(@RequestParam Long ticketId, @RequestParam Integer quantity, HttpSession session) {
User currentUser = (User) session.getAttribute("user");
if (currentUser == null) {
return Result.error("用户未登录,请先登录");
}
// 参数校验
if (quantity <= 0 || quantity > 5) {
return Result.error("购票数量应在1-5张之间");
}
// 检查用户是否有未支付的订单
QueryWrapper<TicketOrder> unpaidWrapper = new QueryWrapper<>();
unpaidWrapper.eq("user_id", currentUser.getId())
.eq("order_status", 0); // 0待支付
Long unpaidCount = ticketOrderMapper.selectCount(unpaidWrapper);
if (unpaidCount > 0) {
return Result.error("您有未支付的订单,请先完成支付或取消后再抢票");
}
// 使用悲观锁查询票务信息,防止并发问题
ConcertTicket ticket = ticketMapper.selectByIdForUpdate(ticketId);
if (ticket == null) {
return Result.error("票务信息不存在");
}
// 检查票务状态
if (ticket.getStatus() != 1) {
return Result.error("该票务暂未开售或已停售");
}
// 检查售票时间
Date currentTime = new Date();
if (currentTime.before(ticket.getSaleStartTime())) {
return Result.error("抢票尚未开始,开售时间:" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(ticket.getSaleStartTime()));
}
if (currentTime.after(ticket.getSaleEndTime())) {
return Result.error("抢票已结束");
}
// 检查库存充足性
if (ticket.getRemainStock() < quantity) {
return Result.error("票务库存不足,剩余:" + ticket.getRemainStock() + "张");
}
// 检查用户购票限制(每人每场限购5张)
QueryWrapper<TicketOrder> limitWrapper = new QueryWrapper<>();
limitWrapper.eq("user_id", currentUser.getId())
.eq("ticket_id", ticketId)
.in("order_status", Arrays.asList(0, 1, 2)); // 待支付、已支付、已出票
Long userPurchasedCount = ticketOrderMapper.selectCount(limitWrapper);
if (userPurchasedCount + quantity > 5) {
return Result.error("每人每场限购5张,您已购买" + userPurchasedCount + "张");
}
// 计算订单金额
BigDecimal unitPrice = ticket.getTicketPrice();
BigDecimal totalAmount = unitPrice.multiply(new BigDecimal(quantity));
// 创建订单
TicketOrder order = new TicketOrder();
order.setUserId(currentUser.getId());
order.setUserName(currentUser.getUsername());
order.setUserPhone(currentUser.getPhone());
order.setTicketId(ticketId);
order.setConcertName(ticket.getConcertName());
order.setConcertType(ticket.getConcertType());
order.setVenue(ticket.getVenue());
order.setShowTime(ticket.getShowTime());
order.setSeatZone(ticket.getSeatZone());
order.setQuantity(quantity);
order.setUnitPrice(unitPrice);
order.setTotalAmount(totalAmount);
order.setOrderStatus(0); // 待支付
order.setCreateTime(currentTime);
order.setOrderNo("ORD" + System.currentTimeMillis() + RandomUtil.randomNumbers(6));
// 计算订单超时时间(15分钟)
Calendar expireCalendar = Calendar.getInstance();
expireCalendar.add(Calendar.MINUTE, 15);
order.setExpireTime(expireCalendar.getTime());
// 扣减库存(关键操作)
int updateStock = ticketMapper.decreaseStock(ticketId, quantity);
if (updateStock == 0) {
throw new RuntimeException("库存扣减失败,可能库存不足");
}
// 保存订单
int insertOrder = ticketOrderMapper.insert(order);
if (insertOrder == 0) {
throw new RuntimeException("订单创建失败");
}
// 更新票务统计
ticket.setSoldCount(ticket.getSoldCount() + quantity);
ticketMapper.updateById(ticket);
// 记录抢票日志
GrabLog grabLog = new GrabLog();
grabLog.setUserId(currentUser.getId());
grabLog.setTicketId(ticketId);
grabLog.setOrderId(order.getId());
grabLog.setQuantity(quantity);
grabLog.setGrabTime(currentTime);
grabLog.setResult("成功");
grabLogMapper.insert(grabLog);
// 记录操作日志
SystemLog log = new SystemLog();
log.setUserId(currentUser.getId());
log.setOperation("抢票成功");
log.setDetails("演唱会:" + ticket.getConcertName() + ",数量:" + quantity + ",订单号:" + order.getOrderNo());
log.setCreateTime(currentTime);
systemLogMapper.insert(log);
// 返回订单信息
Map<String, Object> resultData = new HashMap<>();
resultData.put("orderId", order.getId());
resultData.put("orderNo", order.getOrderNo());
resultData.put("totalAmount", totalAmount);
resultData.put("expireTime", order.getExpireTime());
return Result.success("抢票成功,请在15分钟内完成支付", resultData);
}
/**
* 订单管理 - 订单支付处理
* 处理用户支付订单,验证订单状态、超时检查、支付扣款、更新订单状态
*/
@PostMapping("/payOrder")
@Transactional(rollbackFor = Exception.class)
public Result payOrder(@RequestParam Long orderId, @RequestParam String paymentMethod, HttpSession session) {
User currentUser = (User) session.getAttribute("user");
if (currentUser == null) {
return Result.error("用户未登录");
}
// 使用悲观锁查询订单,防止重复支付
TicketOrder order = ticketOrderMapper.selectByIdForUpdate(orderId);
if (order == null) {
return Result.error("订单不存在");
}
// 验证订单归属
if (!order.getUserId().equals(currentUser.getId())) {
return Result.error("无权操作该订单");
}
// 检查订单状态
if (order.getOrderStatus() != 0) {
if (order.getOrderStatus() == 1) {
return Result.error("订单已支付,请勿重复支付");
} else if (order.getOrderStatus() == 4) {
return Result.error("订单已取消,无法支付");
} else {
return Result.error("订单状态异常");
}
}
// 检查订单是否超时
Date currentTime = new Date();
if (currentTime.after(order.getExpireTime())) {
// 订单超时,自动取消并释放库存
order.setOrderStatus(4); // 已取消
order.setCancelTime(currentTime);
order.setCancelReason("超时未支付自动取消");
ticketOrderMapper.updateById(order);
// 释放库存
ticketMapper.increaseStock(order.getTicketId(), order.getQuantity());
return Result.error("订单已超时,请重新抢票");
}
// 查询用户账户
UserAccount account = userAccountMapper.selectOne(
new QueryWrapper<UserAccount>().eq("user_id", currentUser.getId())
);
if (account == null) {
return Result.error("用户账户不存在,请联系客服");
}
BigDecimal totalAmount = order.getTotalAmount();
// 根据支付方式处理
if ("balance".equals(paymentMethod)) {
// 余额支付
if (account.getBalance().compareTo(totalAmount) < 0) {
return Result.error("账户余额不足,当前余额:" + account.getBalance() + "元");
}
// 扣减余额
account.setBalance(account.getBalance().subtract(totalAmount));
int updateAccount = userAccountMapper.updateById(account);
if (updateAccount == 0) {
throw new RuntimeException("余额扣减失败");
}
} else if ("alipay".equals(paymentMethod)) {
// 支付宝支付(实际应调用支付宝接口,此处简化)
// 模拟支付成功
} else if ("wechat".equals(paymentMethod)) {
// 微信支付(实际应调用微信支付接口,此处简化)
// 模拟支付成功
} else {
return Result.error("不支持的支付方式");
}
// 更新订单状态
order.setOrderStatus(1); // 已支付
order.setPaymentMethod(paymentMethod);
order.setPaymentTime(currentTime);
order.setActualAmount(totalAmount);
int updateOrder = ticketOrderMapper.updateById(order);
if (updateOrder == 0) {
throw new RuntimeException("订单更新失败");
}
// 查询票务信息
ConcertTicket ticket = ticketMapper.selectById(order.getTicketId());
// 生成电子票(简化处理)
for (int i = 0; i < order.getQuantity(); i++) {
ElectronicTicket eTicket = new ElectronicTicket();
eTicket.setOrderId(orderId);
eTicket.setUserId(currentUser.getId());
eTicket.setTicketId(order.getTicketId());
eTicket.setConcertName(order.getConcertName());
eTicket.setShowTime(order.getShowTime());
eTicket.setVenue(order.getVenue());
eTicket.setSeatZone(order.getSeatZone());
eTicket.setSeatNo(generateSeatNo(order.getSeatZone(), ticket.getSoldCount() - order.getQuantity() + i + 1));
eTicket.setTicketCode("TK" + System.currentTimeMillis() + RandomUtil.randomNumbers(8));
eTicket.setQrCode(generateQRCode(eTicket.getTicketCode()));
eTicket.setTicketStatus(0); // 未使用
eTicket.setGenerateTime(currentTime);
electronicTicketMapper.insert(eTicket);
}
// 记录支付流水
PaymentRecord paymentRecord = new PaymentRecord();
paymentRecord.setUserId(currentUser.getId());
paymentRecord.setOrderId(orderId);
paymentRecord.setOrderNo(order.getOrderNo());
paymentRecord.setPaymentMethod(paymentMethod);
paymentRecord.setAmount(totalAmount);
paymentRecord.setPaymentTime(currentTime);
paymentRecord.setStatus(1); // 支付成功
paymentRecordMapper.insert(paymentRecord);
// 更新用户消费统计
UserStatistics statistics = userStatisticsMapper.selectOne(
new QueryWrapper<UserStatistics>().eq("user_id", currentUser.getId())
);
if (statistics == null) {
statistics = new UserStatistics();
statistics.setUserId(currentUser.getId());
statistics.setTotalOrders(1);
statistics.setTotalAmount(totalAmount);
statistics.setTotalTickets(order.getQuantity());
userStatisticsMapper.insert(statistics);
} else {
statistics.setTotalOrders(statistics.getTotalOrders() + 1);
statistics.setTotalAmount(statistics.getTotalAmount().add(totalAmount));
statistics.setTotalTickets(statistics.getTotalTickets() + order.getQuantity());
userStatisticsMapper.updateById(statistics);
}
// 记录操作日志
SystemLog log = new SystemLog();
log.setUserId(currentUser.getId());
log.setOperation("订单支付");
log.setDetails("订单号:" + order.getOrderNo() + ",金额:" + totalAmount + ",方式:" + paymentMethod);
log.setCreateTime(currentTime);
systemLogMapper.insert(log);
return Result.success("支付成功,电子票已生成");
}
/**
* 订单管理 - 订单超时自动取消定时任务
* 定时扫描超时未支付的订单并自动取消,释放库存供其他用户抢票
*/
@Scheduled(cron = "0 */1 * * * ?") // 每分钟执行一次
@Transactional(rollbackFor = Exception.class)
public void cancelExpiredOrders() {
Date currentTime = new Date();
// 查询所有超时未支付的订单
QueryWrapper<TicketOrder> expiredWrapper = new QueryWrapper<>();
expiredWrapper.eq("order_status", 0) // 待支付状态
.lt("expire_time", currentTime); // 过期时间小于当前时间
List<TicketOrder> expiredOrders = ticketOrderMapper.selectList(expiredWrapper);
if (expiredOrders.isEmpty()) {
return;
}
int cancelCount = 0;
int releaseStockTotal = 0;
for (TicketOrder order : expiredOrders) {
try {
// 使用悲观锁重新查询订单,防止并发问题
TicketOrder lockOrder = ticketOrderMapper.selectByIdForUpdate(order.getId());
// 再次确认订单状态(可能在查询后已被支付)
if (lockOrder.getOrderStatus() != 0) {
continue;
}
// 更新订单状态为已取消
lockOrder.setOrderStatus(4);
lockOrder.setCancelTime(currentTime);
lockOrder.setCancelReason("超时未支付自动取消");
ticketOrderMapper.updateById(lockOrder);
// 释放库存
int releaseResult = ticketMapper.increaseStock(lockOrder.getTicketId(), lockOrder.getQuantity());
if (releaseResult > 0) {
releaseStockTotal += lockOrder.getQuantity();
}
// 更新票务已售数量
ConcertTicket ticket = ticketMapper.selectById(lockOrder.getTicketId());
if (ticket != null) {
ticket.setSoldCount(ticket.getSoldCount() - lockOrder.getQuantity());
ticketMapper.updateById(ticket);
}
// 记录取消日志
OrderCancelLog cancelLog = new OrderCancelLog();
cancelLog.setOrderId(lockOrder.getId());
cancelLog.setOrderNo(lockOrder.getOrderNo());
cancelLog.setUserId(lockOrder.getUserId());
cancelLog.setCancelReason("超时未支付");
cancelLog.setCancelType(1); // 系统自动取消
cancelLog.setCancelTime(currentTime);
cancelLog.setReleasedStock(lockOrder.getQuantity());
orderCancelLogMapper.insert(cancelLog);
cancelCount++;
} catch (Exception e) {
// 记录异常,继续处理下一个订单
SystemLog errorLog = new SystemLog();
errorLog.setOperation("订单自动取消异常");
errorLog.setDetails("订单号:" + order.getOrderNo() + ",异常:" + e.getMessage());
errorLog.setCreateTime(currentTime);
systemLogMapper.insert(errorLog);
}
}
// 记录定时任务执行日志
if (cancelCount > 0) {
SystemLog taskLog = new SystemLog();
taskLog.setOperation("订单超时自动取消");
taskLog.setDetails("取消订单数:" + cancelCount + ",释放库存:" + releaseStockTotal + "张");
taskLog.setCreateTime(currentTime);
systemLogMapper.insert(taskLog);
}
}
/**
* 辅助方法 - 生成座位号
*/
private String generateSeatNo(String seatZone, int sequence) {
int row = (sequence - 1) / 20 + 1; // 每排20个座位
int col = (sequence - 1) % 20 + 1;
return seatZone + "-" + row + "排" + col + "座";
}
/**
* 辅助方法 - 生成二维码
*/
private String generateQRCode(String ticketCode) {
// 实际应使用二维码生成库,此处简化返回编码
return "QR_" + ticketCode;
}
六.系统文档展示
结束
💕💕文末获取源码联系 计算机程序员小杨