当前位置: 首页 > news >正文

JAVA羽毛球馆预约源码:线上订场超便捷

以下是一个基于Java的羽毛球馆预约系统源码示例,涵盖核心功能模块与关键代码实现,支持线上订场、实时查询、支付集成等便捷操作:

一、系统架构

  1. 后端框架:Spring Boot 2.7 + Spring MVC + MyBatis Plus
  2. 数据库:MySQL 8.0(存储场馆、场次、订单等数据) + Redis(缓存热门场次信息)
  3. 前端:Vue3 + Element Plus(用户端) + 管理员后台(可选)
  4. 支付集成:支付宝/微信支付SDK
  5. 实时通知:WebSocket(订单状态推送)

二、核心功能模块与源码

1. 场馆与场次管理

数据库设计

sql

CREATE TABLE `court` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '场馆ID', `name` varchar(50) NOT NULL COMMENT '场馆名称', `address` varchar(200) NOT NULL COMMENT '地址', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', PRIMARY KEY (`id`) ); CREATE TABLE `court_slot` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '场次ID', `court_id` bigint NOT NULL COMMENT '关联场馆ID', `date` date NOT NULL COMMENT '日期', `start_time` time NOT NULL COMMENT '开始时间', `end_time` time NOT NULL COMMENT '结束时间', `price` decimal(10,2) NOT NULL COMMENT '单价', `status` tinyint DEFAULT '0' COMMENT '状态(0:可预约 1:已满 2:维护中)', PRIMARY KEY (`id`) );

Java实体类

java

// Court.java @Data public class Court { private Long id; private String name; private String address; private String phone; } // CourtSlot.java @Data public class CourtSlot { private Long id; private Long courtId; private LocalDate date; private LocalTime startTime; private LocalTime endTime; private BigDecimal price; private Integer status; }
2. 场次查询与预约

Controller层

java

@RestController @RequestMapping("/api/court") public class CourtController { @Autowired private CourtSlotService courtSlotService; // 查询可预约场次(按日期筛选) @GetMapping("/available") public Result<List<CourtSlotVO>> getAvailableSlots( @RequestParam Long courtId, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) { List<CourtSlot> slots = courtSlotService.getAvailableSlots(courtId, date); List<CourtSlotVO> vos = slots.stream().map(this::convertToVO).collect(Collectors.toList()); return Result.success(vos); } // 用户预约场次 @PostMapping("/reserve") public Result<String> reserveSlot(@RequestBody ReserveRequest request) { // 参数校验(用户ID、场次ID、预约人数等) if (request.getPersonCount() > 4) { return Result.fail("单场最多预约4人"); } // 调用服务层处理预约逻辑 boolean success = courtSlotService.reserveSlot( request.getUserId(), request.getSlotId(), request.getPersonCount() ); if (success) { return Result.success("预约成功"); } else { return Result.fail("该场次已满或不可用"); } } private CourtSlotVO convertToVO(CourtSlot slot) { CourtSlotVO vo = new CourtSlotVO(); BeanUtils.copyProperties(slot, vo); vo.setStartTimeStr(slot.getStartTime().toString()); vo.setEndTimeStr(slot.getEndTime().toString()); return vo; } }

Service层(关键逻辑)

java

@Service public class CourtSlotServiceImpl implements CourtSlotService { @Autowired private CourtSlotMapper courtSlotMapper; @Autowired private OrderMapper orderMapper; // 订单表操作 @Override public List<CourtSlot> getAvailableSlots(Long courtId, LocalDate date) { // 从Redis缓存中优先获取(减少数据库压力) String cacheKey = "court:available:" + courtId + ":" + date.toString(); String cachedData = redisTemplate.opsForValue().get(cacheKey); if (cachedData != null) { return JSON.parseArray(cachedData, CourtSlot.class); } // 数据库查询 List<CourtSlot> slots = courtSlotMapper.selectList( new QueryWrapper<CourtSlot>() .eq("court_id", courtId) .eq("date", date) .eq("status", 0) // 可预约状态 .orderByAsc("start_time") ); // 写入缓存(设置10分钟过期) if (!slots.isEmpty()) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(slots), 10, TimeUnit.MINUTES); } return slots; } @Override @Transactional public boolean reserveSlot(Long userId, Long slotId, Integer personCount) { // 1. 检查场次状态 CourtSlot slot = courtSlotMapper.selectById(slotId); if (slot == null || slot.getStatus() != 0) { return false; } // 2. 检查是否已满(模拟:假设每场最多4人) // 实际应查询该场次已预约人数(需订单表关联查询) Integer reservedCount = orderMapper.countBySlotId(slotId); if (reservedCount + personCount > 4) { return false; } // 3. 创建订单 Order order = new Order(); order.setUserId(userId); order.setSlotId(slotId); order.setPersonCount(personCount); order.setTotalAmount(slot.getPrice().multiply(new BigDecimal(personCount))); order.setStatus(0); // 待支付 orderMapper.insert(order); // 4. 更新场次状态(若达到上限) if (reservedCount + personCount == 4) { courtSlotMapper.update( null, new UpdateWrapper<CourtSlot>() .set("status", 1) // 已满 .eq("id", slotId) ); // 清除缓存(避免脏数据) redisTemplate.delete("court:available:" + slot.getCourtId() + ":" + slot.getDate().toString()); } return true; } }
3. 支付集成(以支付宝为例)

配置类

java

@Configuration public class AlipayConfig { @Value("${alipay.app-id}") private String appId; @Value("${alipay.merchant-private-key}") private String merchantPrivateKey; @Value("${alipay.alipay-public-key}") private String alipayPublicKey; @Bean public AlipayClient alipayClient() { return new DefaultAlipayClient( "https://openapi.alipay.com/gateway.do", appId, merchantPrivateKey, "json", "UTF-8", alipayPublicKey, "RSA2" ); } }

支付服务

java

@Service public class PaymentService { @Autowired private AlipayClient alipayClient; @Autowired private OrderMapper orderMapper; public String createPayment(Long orderId) throws AlipayApiException { Order order = orderMapper.selectById(orderId); if (order == null || order.getStatus() != 0) { throw new RuntimeException("订单不存在或已支付"); } AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setReturnUrl("http://yourdomain.com/pay/success"); // 支付成功回调 request.setNotifyUrl("http://yourdomain.com/pay/notify"); // 异步通知 JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", orderId.toString()); bizContent.put("total_amount", order.getTotalAmount().toString()); bizContent.put("subject", "羽毛球馆预约-" + orderId); bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); request.setBizContent(bizContent.toJSONString()); AlipayTradePagePayResponse response = alipayClient.pageExecute(request); if (response.isSuccess()) { return response.getBody(); // 返回支付页面HTML } else { throw new RuntimeException("支付请求失败: " + response.getSubMsg()); } } // 支付回调处理(异步通知) @Transactional public void handlePaymentNotify(Map<String, String> params) { String orderId = params.get("out_trade_no"); String tradeStatus = params.get("trade_status"); if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) { Order order = orderMapper.selectById(Long.valueOf(orderId)); if (order != null && order.getStatus() == 0) { order.setStatus(1); // 已支付 orderMapper.updateById(order); // 可选:发送短信/WebSocket通知用户 // websocketService.sendToUser(order.getUserId(), "支付成功!订单号:" + orderId); } } } }
4. 前端交互示例(Vue3)

vue

<template> <div class="court-reserve"> <h2>选择场次</h2> <el-date-picker v-model="selectedDate" type="date" placeholder="选择日期" @change="loadSlots" /> <div class="slot-list"> <div v-for="slot in availableSlots" :key="slot.id" class="slot-item"> <div class="time">{{ slot.startTimeStr }} - {{ slot.endTimeStr }}</div> <div class="price">¥{{ slot.price }}</div> <el-button type="primary" :disabled="slot.status !== 0" @click="handleReserve(slot)" > {{ slot.status === 0 ? '立即预约' : '已满' }} </el-button> </div> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { ElMessage } from 'element-plus'; import { getAvailableSlots, reserveSlot } from '@/api/court'; const selectedDate = ref(new Date()); const availableSlots = ref([]); const courtId = ref(1); // 假设当前场馆ID为1 const loadSlots = async () => { try { const res = await getAvailableSlots(courtId.value, selectedDate.value); availableSlots.value = res.data; } catch (error) { ElMessage.error('加载场次失败'); } }; const handleReserve = async (slot) => { try { const res = await reserveSlot({ userId: 123, // 实际应从登录状态获取 slotId: slot.id, personCount: 2 }); if (res.success) { ElMessage.success('预约成功!请尽快支付'); // 跳转至支付页面 window.location.href = `/pay?orderId=${res.data.orderId}`; } else { ElMessage.error(res.message || '预约失败'); } } catch (error) { ElMessage.error('系统错误,请重试'); } }; onMounted(() => { loadSlots(); }); </script>

三、关键优化点

  1. 缓存策略:使用Redis缓存热门场次数据,减少数据库压力。
  2. 并发控制:通过数据库唯一索引或分布式锁防止超卖(如同一场次被多人同时预约)。
  3. 支付安全:支付回调需验证签名,防止伪造通知。
  4. 用户体验
    • 前端展示剩余可预约人数(如“剩余2/4人”)。
    • 支付超时自动取消订单(可通过定时任务扫描未支付订单)。

四、扩展功能建议

  1. 会员体系:积分抵扣、折扣券、会员专享场次。
  2. 多人拼场:允许用户发起拼场,系统自动匹配。
  3. 设备关联:预约时选择球拍、羽毛球等设备(需额外库存管理)。
  4. 数据统计:场馆利用率、高峰时段分析等。

此源码可直接集成至现有项目,或作为独立服务部署。实际开发中需根据业务需求调整字段与逻辑(如退款流程、取消预约规则等)。

http://www.jsqmd.com/news/166098/

相关文章:

  • iPaaS平台选型全景图:五大平台核心能力与战略价值解析
  • MaopaiJD 规则的对象共同参与才合法
  • 为什么星巴克最小杯是 12 盎司 “高杯”,而非 8 盎司普通杯?
  • 2025年AI大模型发展趋势深度解析:从应用到技术的全方位预测
  • Anaconda替换方案实测:Miniconda-Python3.9内存占用降低60%
  • 比Anaconda安装教程更高效:5分钟跑通PyTorch GPU环境
  • 2025年高质感20个项目管理软件,性价比高的协作精品
  • 2026年 砂尘试验箱/军标砂尘试验箱厂家权威推荐榜:专业制造与严苛环境模拟实力解析 - 品牌企业推荐师(官方)
  • 为什么很多餐厅都给饮料免费续杯?
  • 2026年盐雾试验箱厂家权威推荐榜:复合/小型/中性/进口/智能/交变/循环/步入式盐雾试验箱,专业耐腐蚀测试设备精选指南 - 品牌企业推荐师(官方)
  • Linux用户必看:Miniconda-Python3.9在Ubuntu/CentOS下的表现对比
  • 掌握大模型技术:一份从入门到精通的收藏级学习资源_大模型入门学习教程(非常详细)
  • 护眼台灯怎么选?2025学生首选推荐-6款热门护眼台灯测评对比 - 资讯焦点
  • Pyenv uninstall删除不需要的Python版本节省空间
  • 选对厂家不踩坑!2025年重型货架实力厂商综合评测,流利货架定制/手摇式板材货架/伸缩货架/重型伸缩悬臂货架重型货架公司推荐排行榜单 - 品牌推荐师
  • 谁是你公司的 IT「神医」?
  • JAVA同城上门服务源码:洗车养护轻松搞定
  • Jupyter Lab预加载PyTorch库:Miniconda-Python3.9提升交互式编程效率
  • Elasticsearch搜索过程深度解析:详细解答
  • 收藏!Graph RAG 工作原理与完整流程详解(小白程序员友好)
  • TFDmemtable 新版内存表CopyDataSet选项说明
  • GitHub热门项目复现难?Miniconda-Python3.9镜像精准还原实验环境
  • Markdown笔记嵌入代码块:Miniconda-Python3.9支持AI项目文档一体化
  • 收藏备用!大模型入门必学:Prompt从基础到实战全攻略
  • 2025年湖北专业的文化石供应商找哪家,贴墙石/碎拼石/文化石/石材/脚踏石/天然石/地铺石供应商口碑推荐榜 - 品牌推荐师
  • Conda init命令失效?Miniconda-Python3.9出厂即激活base环境
  • 专精特新企业认定委托代理哪家好?盘点这五家2026年口碑代办公司机构助你一次通过 - 速递信息
  • GitHub上的璀璨明星:10个令人惊叹的AI Agent开发平台!
  • 收藏!大模型赛道全攻略:从入门认知到就业保研精准导航
  • HTML表格生成自动化:Miniconda-Python3.9配合pandas输出报告