目录
一、项目背景
二、技术介绍
三、功能介绍
四、代码设计
五、系统实现
六、源码 获取
一、项目背景
随着全民健身国家战略的深入实施和健康中国建设的持续推进,羽毛球作为一项门槛低、趣味性强、运动效果显著的大众体育运动,近年来参与人数呈现爆发式增长。据统计,我国羽毛球爱好者已超过2.5亿人,羽毛球运动已成为仅次于跑步的第二大全民健身项目。然而,与日益增长的运动需求形成鲜明对比的是,羽毛球场地资源的严重短缺以及传统场馆管理模式的滞后。
当前,绝大多数羽毛球馆仍采用电话预约、现场排队或人工登记等传统管理模式。这些方式存在诸多弊端:一方面,用户需要在营业时间内拨打电话或亲自前往场馆,预约过程耗时费力,且难以实时了解场地空闲状态,常常出现“想打球没场地、有场地没人知道”的资源错配现象;另一方面,场馆管理者面临人工成本高、场地利用率低、订单管理混乱、财务统计繁琐等问题,严重影响运营效率和服务质量。尤其在节假日或周末高峰时段,场地供不应求的矛盾更加突出,用户预约体验差、爽约率高、场地空置与排队等候并存等问题频发。
移动互联网技术的普及为这一问题的解决提供了新思路。微信小程序凭借其“无需下载、即用即走、触达便捷”的天然优势,已成为连接线下服务与线上用户的最佳载体。基于微信小程序开发羽毛球馆预约系统,能够为用户提供场地查询、在线预约、订单管理、支付结算等一站式便捷服务,同时为场馆管理者提供订单统计、场地管理、数据分析等智能化运营工具。系统还可集成会员管理、优惠券发放、约球组局等社交功能,增强用户粘性,提升场馆综合收益。
本课题旨在设计并实现一款基于微信小程序的羽毛球馆预约系统,借助移动互联网技术优化场馆资源配置,改善用户预约体验,推动传统体育场馆向数字化、智能化管理转型,具有显著的社会价值与市场应用前景。
二、技术介绍
本系统采用前后端分离的架构模式,前端基于微信小程序原生框架开发,后端采用SpringBoot整合MyBatis构建,数据存储选用MySQL关系型数据库,并结合Redis实现缓存优化。
前端技术选型:微信小程序依托微信庞大的用户生态,具备“无需下载、即用即走”的天然优势。前端使用小程序原生框架,采用WXML、WXSS与JavaScript进行页面开发,通过组件化思想构建场地展示、预约下单、订单管理等核心界面。小程序提供的登录授权、地图定位、支付接口等能力,能够无缝对接微信生态,为用户提供流畅便捷的预约体验。
后端技术选型:SpringBoot作为核心框架,凭借其“约定优于配置”的设计理念,简化了项目搭建与开发配置。采用分层架构设计,包含Controller控制层、Service业务层、Mapper数据访问层,确保代码结构清晰、职责单一。MyBatis作为持久层框架,通过注解与XML映射的方式实现数据库操作,既保留了SQL的灵活性,又实现了对象关系映射的便捷性。Redis用于缓存场地状态、用户会话等高频访问数据,减轻数据库压力,提升系统响应速度。
数据库与接口设计:MySQL存储用户信息、场地数据、订单记录、支付流水等核心业务数据。前后端通过RESTful API进行交互,数据格式统一采用JSON,微信小程序端调用后端接口完成业务逻辑处理。整体架构实现了前后端完全分离,便于独立开发、测试与部署,确保系统的高可用性与可扩展性。
三、功能介绍
本系统面向两类用户角色设计,分别为普通用户与场馆管理员,提供全方位的羽毛球馆预约管理功能。
用户端功能:用户通过微信授权一键登录系统,无需繁琐注册。首页以日历与时间轴相结合的方式直观展示场地空闲状态,支持按日期、时段、场地类型筛选可预约场地。用户选择场地后进入预约确认页面,填写预约时段、参与人数等信息并完成在线支付,生成电子订单。我的模块集成了订单管理、预约记录、取消预约等功能,用户可随时查看当前预约、历史订单及消费明细。系统还提供约球组局功能,用户可发布约球信息、邀请球友组队,增强社交互动性。收藏场地功能方便用户快速预约常去场馆。
管理端功能:管理员通过独立入口登录后台管理系统,实现场地信息的增删改查,灵活设置场地开放时间、时段价格及特殊时段优惠策略。订单管理模块支持查看所有用户订单,对异常订单进行手动处理,并提供订单统计报表。财务管理功能自动生成每日、每周、每月营收统计,支持数据导出。场地状态监控实时显示各场地当前使用情况,支持管理员手动锁定或解锁场地。系统还具备会员管理功能,可设置会员等级、发放优惠券,提升用户粘性。
特色功能:系统集成微信支付,实现预约-支付-核销闭环;预约成功后生成电子核销码,用户到场出示二维码由管理员扫码核销;爽约自动记录并影响用户信用分,信用分过低将限制预约权限,有效降低爽约率。
四、代码设计
@Servicepublic class BookingServiceImpl implements BookingService { @Autowired private BookingOrderMapper bookingOrderMapper; @Autowired private CourtMapper courtMapper; @Autowired private UserMapper userMapper; @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String COURT_LOCK_PREFIX = "court:lock:"; @Override @Transactional public BookingResult createOrder(BookingDTO bookingDTO) { Integer userId = bookingDTO.getUserId(); Integer courtId = bookingDTO.getCourtId(); LocalDateTime startTime = bookingDTO.getStartTime(); LocalDateTime endTime = bookingDTO.getEndTime(); // 1. 校验场地是否存在且启用 Court court = courtMapper.selectById(courtId); if (court == null || court.getStatus() != 1) { throw new BusinessException("场地不存在或已禁用"); } // 2. 校验预约时间合法性 if (startTime.isBefore(LocalDateTime.now())) { throw new BusinessException("不能预约过去的时间"); } if (Duration.between(startTime, endTime).toHours() < 1) { throw new BusinessException("预约时长至少1小时"); } // 3. 分布式锁防止超卖 String lockKey = COURT_LOCK_PREFIX + courtId + ":" + startTime; Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", Duration.ofSeconds(30)); if (Boolean.FALSE.equals(locked)) { throw new BusinessException("该时段正在被其他用户预约,请稍后再试"); } try { // 4. 检查时间冲突 int conflict = bookingOrderMapper.checkTimeConflict( courtId, startTime, endTime); if (conflict > 0) { throw new BusinessException("该时段已被预约,请选择其他时间"); } // 5. 检查用户信用分 User user = userMapper.selectById(userId); if (user.getCreditScore() < 60) { throw new BusinessException("信用分过低,暂无法预约"); } // 6. 计算金额 long hours = Duration.between(startTime, endTime).toHours(); BigDecimal amount = court.getPrice().multiply(BigDecimal.valueOf(hours)); // 7. 创建订单 BookingOrder order = new BookingOrder(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setCourtId(courtId); order.setStartTime(startTime); order.setEndTime(endTime); order.setAmount(amount); order.setStatus(0); // 待支付 order.setQrCode(generateQrCode(order.getOrderNo())); order.setCreateTime(LocalDateTime.now()); bookingOrderMapper.insert(order); // 8. 订单超时取消任务(30分钟未支付自动取消) scheduleCancelOrder(order.getId(), order.getOrderNo()); return BookingResult.builder() .orderId(order.getId()) .orderNo(order.getOrderNo()) .amount(amount) .qrCode(order.getQrCode()) .build(); } finally { redisTemplate.delete(lockKey); } } @Override @Transactional public PayResult payOrder(PayDTO payDTO) { String orderNo = payDTO.getOrderNo(); Integer userId = payDTO.getUserId(); // 1. 查询订单 BookingOrder order = bookingOrderMapper.selectOne( new LambdaQueryWrapper<BookingOrder>() .eq(BookingOrder::getOrderNo, orderNo) .eq(BookingOrder::getUserId, userId)); if (order == null) { throw new BusinessException("订单不存在"); } if (order.getStatus() != 0) { throw new BusinessException("订单状态异常,无法支付"); } // 2. 调用微信支付统一下单接口 WechatPayRequest request = new WechatPayRequest(); request.setOutTradeNo(orderNo); request.setTotalFee(order.getAmount().multiply(BigDecimal.valueOf(100)).intValue()); request.setBody("羽毛球馆场地预约"); request.setOpenId(payDTO.getOpenId()); WechatPayResponse response = wechatPayService.unifiedOrder(request); // 3. 更新订单支付流水号 order.setPayNo(response.getPrepayId()); bookingOrderMapper.updateById(order); return PayResult.builder() .prepayId(response.getPrepayId()) .nonceStr(response.getNonceStr()) .timeStamp(response.getTimeStamp()) .signType("MD5") .paySign(response.getPaySign()) .build(); } @Override @Transactional public void cancelOrder(Integer orderId, Integer userId) { BookingOrder order = bookingOrderMapper.selectById(orderId); if (order == null || !order.getUserId().equals(userId)) { throw new BusinessException("订单不存在"); } // 待支付订单直接取消 if (order.getStatus() == 0) { order.setStatus(2); bookingOrderMapper.updateById(order); } // 已支付订单需提前2小时取消,且扣除信用分 else if (order.getStatus() == 1) { if (order.getStartTime().isBefore(LocalDateTime.now().plusHours(2))) { throw new BusinessException("距离预约开始不足2小时,无法取消"); } // 发起退款 wechatPayService.refund(order.getPayNo(), order.getAmount()); order.setStatus(4); bookingOrderMapper.updateById(order); // 扣除用户信用分 User user = userMapper.selectById(userId); user.setCreditScore(user.getCreditScore() - 5); userMapper.updateById(user); } else { throw new BusinessException("当前订单状态不可取消"); } } private String generateOrderNo() { return "BOOK" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } private String generateQrCode(String orderNo) { // 生成核销二维码,内容为订单编号 return "https://api.example.com/qrcode/" + orderNo; }}
五、系统实现





六、源码 获取
大家点赞、收藏、关注、评论啦 、👇🏻获取联系方式在文章末尾👇🏻