2026/1/8 15:00:59
网站建设
项目流程
网站在建设中模板,网页设计图片横排代码,wordpress iconfont,wordpress 角色等级以下是一个基于Java的羽毛球馆预约系统源码示例#xff0c;涵盖核心功能模块与关键代码实现#xff0c;支持线上订场、实时查询、支付集成等便捷操作#xff1a;一、系统架构后端框架#xff1a;Spring Boot 2.7 Spring MVC MyBatis Plus数据库#xff1a;MySQL 8.0涵盖核心功能模块与关键代码实现支持线上订场、实时查询、支付集成等便捷操作一、系统架构后端框架Spring Boot 2.7 Spring MVC MyBatis Plus数据库MySQL 8.0存储场馆、场次、订单等数据 Redis缓存热门场次信息前端Vue3 Element Plus用户端 管理员后台可选支付集成支付宝/微信支付SDK实时通知WebSocket订单状态推送二、核心功能模块与源码1. 场馆与场次管理数据库设计sqlCREATE 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层javaRestController RequestMapping(/api/court) public class CourtController { Autowired private CourtSlotService courtSlotService; // 查询可预约场次按日期筛选 GetMapping(/available) public ResultListCourtSlotVO getAvailableSlots( RequestParam Long courtId, RequestParam DateTimeFormat(pattern yyyy-MM-dd) LocalDate date) { ListCourtSlot slots courtSlotService.getAvailableSlots(courtId, date); ListCourtSlotVO vos slots.stream().map(this::convertToVO).collect(Collectors.toList()); return Result.success(vos); } // 用户预约场次 PostMapping(/reserve) public ResultString 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层关键逻辑javaService public class CourtSlotServiceImpl implements CourtSlotService { Autowired private CourtSlotMapper courtSlotMapper; Autowired private OrderMapper orderMapper; // 订单表操作 Override public ListCourtSlot 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); } // 数据库查询 ListCourtSlot slots courtSlotMapper.selectList( new QueryWrapperCourtSlot() .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 UpdateWrapperCourtSlot() .set(status, 1) // 已满 .eq(id, slotId) ); // 清除缓存避免脏数据 redisTemplate.delete(court:available: slot.getCourtId() : slot.getDate().toString()); } return true; } }3. 支付集成以支付宝为例配置类javaConfiguration 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 ); } }支付服务javaService 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(MapString, 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. 前端交互示例Vue3vuetemplate div classcourt-reserve h2选择场次/h2 el-date-picker v-modelselectedDate typedate placeholder选择日期 changeloadSlots / div classslot-list div v-forslot in availableSlots :keyslot.id classslot-item div classtime{{ slot.startTimeStr }} - {{ slot.endTimeStr }}/div div classprice¥{{ slot.price }}/div el-button typeprimary :disabledslot.status ! 0 clickhandleReserve(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三、关键优化点缓存策略使用Redis缓存热门场次数据减少数据库压力。并发控制通过数据库唯一索引或分布式锁防止超卖如同一场次被多人同时预约。支付安全支付回调需验证签名防止伪造通知。用户体验前端展示剩余可预约人数如“剩余2/4人”。支付超时自动取消订单可通过定时任务扫描未支付订单。四、扩展功能建议会员体系积分抵扣、折扣券、会员专享场次。多人拼场允许用户发起拼场系统自动匹配。设备关联预约时选择球拍、羽毛球等设备需额外库存管理。数据统计场馆利用率、高峰时段分析等。此源码可直接集成至现有项目或作为独立服务部署。实际开发中需根据业务需求调整字段与逻辑如退款流程、取消预约规则等。