【Java】分布式演唱会抢票系统 SpringBoot+Vue框架 计算机毕业设计项目 Idea+Navicat+MySQL安装 附源码+文档+讲解

82 阅读7分钟

前言

💖💖作者:计算机程序员小杨 💙💙个人简介:我是一名计算机相关专业的从业者,擅长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;
}

六.系统文档展示

在这里插入图片描述

结束

💕💕文末获取源码联系 计算机程序员小杨