每年到这个节点,都会有很多计算机专业的同学卡在毕业设计选题和实现上。如果你正在找一个"难度适中、需求明确、技术栈主流、还能轻松演示"的题目,SpringBoot + Vue这套前后端分离方案做的羽毛球场地预约系统,其实是个很值得考虑的方向。它不像商城系统那么烂大街,也不像算法类题目那样需要极强的理论基础,业务逻辑清晰,数据模型直观,又能把后端框架、前端交互、数据库设计都串起来,是一个很适合拿来练手也适合拿来答辩的项目。
这篇文章我按照实际做毕设的流程来拆解:需求从哪来、表怎么建、后端接口怎么设计、前端页面怎么做、踩过哪些坑、答辩时老师喜欢问什么。内容基于我实际开发类似系统的经验,也参考了网上很多毕业设计项目的通用做法,你可以把它当作一份可以直接照着做的项目笔记。
1. 需求拆解:别急着写代码,先想清楚这个系统到底要管什么
很多人拿到一个题目就急着建项目、配环境、敲代码,结果写到一半发现业务逻辑没想清楚,反复推倒重来。预约类系统的核心问题不是"怎么做页面",而是"一套场地在某个时段到底能不能被预约",以及"预约之后钱怎么算、取消怎么处理、爽约怎么办"。我建议拿到题目的第一件事,是把角色、功能、状态流转这三件事彻底想明白。
1.1 角色划分决定功能边界
羽毛球场地预约系统里,最基础的参与者有两类:普通用户和场馆管理员。有些项目还会拆出"系统管理员"来管理场地的上下架、统计营收报表,但如果你只做两级角色,也完全够用。
用户端需要的能力很直观:
- 注册登录、个人信息维护、密码修改
- 浏览场地列表,按羽毛球场地类型、时段、价格过滤
- 选择场地和具体时间段,提交预约订单
- 在线支付或者在到场时核销(取决于你的项目设定)
- 查看自己的预约记录,支持取消订单
- 查看自己的消费记录、充值记录
管理端的能力则是另一套逻辑:
- 场地信息的增删改查,包括场地编号、位置、可容纳人数、按时段的价格
- 场地开放时段的配置,比如周一到周五几点到几点可用
- 订单管理:查看所有订单、手动取消、确认到场核销
- 用户管理:查看注册用户列表、封禁异常用户
- 数据统计:每日预约量、营收情况、热门时段分析
把这些功能列成表格,其实就是一个完整的需求规格说明书,答辩的时候直接能用。
1.2 状态流转是预约系统的灵魂
预约系统的最大难点,不是增删改查,而是订单状态的管理。一个订单从创建到最后结束,通常要经历这样几条路径:
正常路径:待支付 → 已支付/待使用 → 已使用(核销完成) → 已完成
取消路径:待支付 → 已取消(用户取消或超时未支付自动取消);已支付 → 已取消(管理员取消,同时触发退款)
异常情况:用户预约了但没到场,这个订单怎么标识;场地临时维护,管理员取消订单,用户余额如何退回
我在设计系统时,习惯把订单状态定义成一个枚举,用数字标识状态,而不是用字符串散落在代码里。常见的定义方式是:
- 0:待支付
- 1:已支付 / 待使用
- 2:已使用
- 3:已取消
- 4:已退款
- 5:已爽约(预约未到场)
状态定义清楚了,后续写接口、写页面、做统计都会非常顺手。很多同学忽略这一点,订单表里直接放个字符串status,一会儿写"已支付"一会儿写"1",后面写SQL统计数据的时候简直痛不欲生。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型:为什么是这个组合,以及还有哪些可替换方案
2.1 后端选SpringBoot的理由
SpringBoot在当前后端就业市场里的地位不用多说。对于毕设来说,它最大的优势是"约定优于配置"——不需要像早期SSH那样写一大堆XML配置,一个启动类就能把项目跑起来。而且SpringBoot天然集成了Spring MVC、自动配置、内嵌Tomcat,配合MyBatis Plus操作数据库,开发效率非常高。
这个题目用SpringBoot做后端,还有一个潜在的好处是后续扩展方便。比如你要加一个微信小程序端,后端接口是现成的,只需要在小程序里对接HTTP接口即可;你要是想加消息通知,SpringBoot整合WebSocket或者第三方短信SDK都非常简单。这些都可以写在论文的"展望"章节里作为加分项。
2.2 前端选Vue的理由
Vue在国内前端社区的普及度极高,学习曲线平缓,文档友好。配合Element Plus组件库,做管理后台类的界面基本上就是"拼积木"——表格、表单、弹窗、日期选择器都是现成的。Vue的响应式数据和组件化开发,也让代码的可维护性远高于传统的JQuery多页面写法。
需要说明的是,新版Vue生态已经默认走Vite构建工具,而不是Vue CLI(Webpack)。很多同学在网上搜到的是旧教程,用vue create命令创建项目,但新项目我建议直接npm create vue@latest。这一点在后面运行部署的部分会重点讲。
2.3 关于技术栈的一句话总结
SpringBoot + Vue + MySQL + MyBatis Plus + Element Plus,是当前毕设项目中非常主流的组合。核心原因有三:
- 技术栈覆盖面广,能体现你对前后端分离、RESTful API、ORM框架的理解
- 社区资料丰富,遇到问题几乎都能搜到解决方案
- 就业导向明确,这套技术栈在企业级项目中广泛应用,答辩时说服力强
如果你有精力,还可以引入Redis做验证码缓存和热点数据缓存,引入Spring Security或Sa-Token做权限控制,这些都能在论文里成为"技术亮点"。
3. 数据库设计:预约系统的表结构到底该怎么建
数据库设计是答辩老师最喜欢深挖的部分。我见过不少同学表结构极简,用户表、场地表、订单表三张表走天下,订单里塞一个"备注"字段记录所有额外信息。这种设计虽然能跑通,但很难经得起追问。
预约系统的核心表我建议按这样划分:用户表(user)、场地表(venue或court)、场地时段表(venue_slot)、订单表(order_info)、订单明细表(order_detail)、充值/账单表(user_account_log)。
3.1 核心表的字段设计
用户表(user)
用户表相对常规:id、username、password(加密存储,建议BCrypt)、nickname、phone、avatar、role(区分普通用户和管理员)、status(是否封禁)、create_time。有一点需要注意,涉及账户余额的项目,建议把balance字段单独放用户表里,或者拆出一张账户表,避免每次都要关联账单表查余额。
场地表(venue)
场地表包含:id、venue_name(场地名称,比如"1号场""2号场")、venue_type(室内/室外)、location(位置描述)、price_per_hour(每小时价格)、max_people(可容纳人数)、status(启用/停用)、description、image_url。如果你设计的系统还支持按具体时段动态调价(比如工作日和周末价格不同),那就需要把价格字段放到时段表里。
场地时段表(venue_slot)
这是预约系统里非常关键的一张表。它的作用是把"某块场地在某个时间段是否可预约"这一信息结构化存储。字段包括:id、venue_id、start_time、end_time、week_day(星期几,支持每周重复配置)、slot_price(该时段的价格)、status(可预约/已约满/维护中)。
为什么要拆出这张表,而不是直接在场地表里写一个"可预约时间"字段?因为在真实场景中,不同场地的时段配置是不一样的,而且时段状态需要跟着订单变化而实时更新。你当然也可以通过代码锁定"每小时为一个预约单位",这样时段表相对固定,但拆一张表出来更灵活,且答辩时更有东西可讲。
订单表(order_info)
订单表字段比较多,核心的有:id、order_no(订单编号,建议用时间戳+随机数生成,方便作为业务标识)、user_id、venue_id、slot_id(预约的具体时段)、order_date(预约的日期)、start_time、end_time、order_amount(订单金额)、pay_status(支付状态)、order_status(订单状态,对应前面说的枚举)、create_time、pay_time、cancel_time。把预约日期和时段开始结束时间冗余到订单表里,虽然违反了部分范式要求,但能极大地简化查询逻辑——查"某个用户某天有哪些预约"和查"某个时段是否被占"都只需要表级查询。
订单明细表(order_detail)
有些系统只提供一个场地时段的预约,那order_detail其实可以合并进订单表。但如果一个订单可以同时预约多个连续时段(比如用户一次性预约下午2点到5点,预定1号场地三个小时),明细表就会更加清晰。它包含id、order_id、venue_id、slot_id、start_time、end_time、price、subtotal。
3.2 需要特别说明的设计决策
第一个决策是时段冲突检测。在设计预约逻辑时,最核心的问题是:用户A预约了1号场地周六14:00-15:00,用户B在14:30想预约同一个场地时必须被拒绝。这里有两种实现方式。
一种是在venue_slot表里,把每个小时都拆成独立记录,用户预约时直接对该时段记录做"UPDATE ... WHERE status = 0"的原子操作,利用数据库行锁避免并发问题。
另一种是只存订单表,查询时通过SQL条件判断时间区间是否重叠:start_time < 新订单的end_time 且 end_time > 新订单的start_time。这种方式灵活,但在并发请求下容易出现超卖。
在实际项目中,我建议把两种方式结合起来:venue_slot表预置时段数据,同时订单表记录预约明细;下单时先更新venue_slot的状态,用数据库行锁保证同一时段的并发预约只成功一个。
第二个决策是价格信息要不要冗余。用户在预约时看到的价格,和下单时实际支付的价格必须一致。有些同学只在场地表存一个价格,然后在前端页面计算总价,这样非常危险——用户完全可以篡改金额再提交。正确的做法是:后端根据耗时和对应时段单价重新计算金额,前端显示的价格只是参考。
4. 后端核心模块实现:从登录鉴权到下单逻辑
整个SpringBoot后端里,我认为有四个模块最能体现工作量,也最值得在论文里详细写:登录鉴权、场地查询、预约下单、订单管理。下面逐个说实现思路和关键代码。
4.1 登录鉴权:用JWT还是Session
毕设项目里,Session方案简单,JWT方案更符合当前企业开发的习惯。推荐使用JWT,理由如下:前后端分离架构下,后端不存储会话状态,扩展性好;JWT本身携带用户信息和过期时间,配合拦截器就能实现接口鉴权;写论文时有"现代认证机制"章节可写。
JWT的基本流程是:
- 用户提交用户名密码,后端校验成功后生成token返回
- 前端把token存到localStorage,每次请求在Header里带上Authorization: Bearer token
- 后端写一个拦截器,解析token,把userId放到ThreadLocal或请求上下文里
- 对需要管理员权限的接口,再校验用户角色
生成JWT的过程用jjwt库非常简单,核心代码大概如下:
java复制// 生成token
String token = Jwts.builder()
.setSubject(userId.toString())
.claim("role", user.getRole())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
有一点要提醒,使用JWT时不要往token里塞太多数据,用户昵称、手机号这些都没必要;token一旦生成就不可撤销,所以如果做了封禁用户功能,封禁判断必须在拦截器里额外查一次数据库或Redis。
4.2 场地时段查询接口
场地时段查询是前端页面最核心的数据来源。用户打开预约页面,会看到场地列表,每个场地显示当天的可用时段。接口设计成GET /api/venue/available?date=2025-06-01&type=badminton就比较好。
这个接口的逻辑是:先从venue表查出所有启用的场地,再查venue_slot表拿到每个场地当天的时段配置,最后排除掉已经被下单占用的时段。用SQL实现大概是这样:
sql复制SELECT vs.* FROM venue_slot vs
WHERE vs.venue_id = #{venueId}
AND vs.status = 1
AND vs.id NOT IN (
SELECT oi.slot_id FROM order_info oi
WHERE oi.order_date = #{date}
AND oi.order_status IN (0, 1)
)
注意order_status过滤条件,订单状态为"待支付"和"已支付"的时段都算被占用。待支付订单就算还没付款,也应该锁定时段,否则会出现两个人同时预约同一时段的不可控问题。
4.3 预约下单与并发控制
下单是整个系统里最需要谨慎的接口。我在之前的一个项目里,就出现过两个用户几乎同时提交同一个场地的同一个时段,结果两个订单都创建成功的情况。原因就是先查时段状态,再插入订单,查询和插入之间没有原子性保障。
正确的做法,是使用数据库的乐观锁或行锁机制。MyBatis Plus里可以通过UpdateWrapper加上条件更新:
java复制boolean updated = venueSlotService.update(
new LambdaUpdateWrapper<VenueSlot>()
.eq(VenueSlot::getId, slotId)
.eq(VenueSlot::getStatus, 1)
.set(VenueSlot::getStatus, 2)
);
if (!updated) {
throw new BusinessException("该时段已被预约,请选择其他时间");
}
这条SQL通过UPDATE ... WHERE id = ? AND status = 1,利用数据库的行锁确保同一时刻只有一个请求能更新成功。更新成2表示该时段已被占用,然后才去创建订单。如果订单创建失败,比如用户余额不足,需要把时段状态回滚为1。
下单时的金额计算,也必须放在后端,不能相信前端传过来的金额。根据slog_id查出该时段的单价,乘以预约时长,才是订单金额:
java复制VenueSlot slot = venueSlotService.getById(slotId);
BigDecimal amount = slot.getSlotPrice()
.multiply(new BigDecimal(slot.getDurationHours()));
为了应对用户点击"提交订单"后迟迟不支付的情况,建议订单状态设置为待支付,并记录create_time。可以用定时任务扫描超过15分钟未支付的订单,自动取消并释放时段。这在答辩时是一个非常亮眼的操作,因为它体现了你对真实业务场景的考虑。
4.4 订单管理接口
订单管理包含用户侧和管理员侧。用户侧需要的接口是:查询我的订单、取消订单、查看订单详情。管理员侧需要的是:查询全部订单(可分页、按状态过滤)、手动取消/退款。
取消订单的逻辑要注意一点:只有待支付和已支付的订单才能取消;已使用的订单不能再取消。取消已支付订单时,要把金额退回到用户余额,并记录一条账务流水。退款操作建议用事务管理,保证余额更新和订单状态更新要么都成功,要么都失败。
java复制@Transactional
public void cancelOrder(Long orderId) {
OrderInfo order = orderInfoService.getById(orderId);
if (order == null || order.getOrderStatus() == 3) {
throw new BusinessException("订单不存在或已取消");
}
if (order.getOrderStatus() == 0) {
// 待支付订单直接取消
order.setOrderStatus(3);
} else if (order.getOrderStatus() == 1) {
// 已支付订单需要退款
userService.increaseBalance(order.getUserId(), order.getOrderAmount());
order.setOrderStatus(3);
}
orderInfoService.updateById(order);
// 释放场地时段
venueSlotService.update(new LambdaUpdateWrapper<VenueSlot>()
.eq(VenueSlot::getId, order.getSlotId())
.set(VenueSlot::getStatus, 1));
}
5. 前端页面设计:Vue + Element Plus 的用户端和管理端实践
前端部分我按用户端和管理端两条线来说。用户端重点是预约流程的交互体验,管理端重点是信息表格化和统计可视化。
5.1 用户端核心页面
用户端的主要页面包括:首页(场地列表)、预约页、我的订单页、个人中心页、登录注册页。
场地列表页:用户进入系统后,首先看到的是所有羽毛球场地卡片。每张卡片展示场地图片、名称、位置、价格。顶部用筛选器按类型(室内/室外)过滤。这个页面比较简单,基本就是调用GET /api/venue/list接口,用v-for渲染卡片。
预约页:这是交互最复杂的页面。用户选择某个场地后,进入预约页。页面顶部显示场地信息,中间是一个日期选择器(默认今天),下面是用表格或卡片形式展示的时段列表。每个时段显示开始时间、结束时间、价格、预约按钮。如果该时段已被预约,则按钮置灰并显示"已约满"。
核心代码如下(Vue 3组合式API风格):
vue复制<template>
<div class="slot-list">
<el-card v-for="slot in availableSlots" :key="slot.id" class="slot-card">
<div class="slot-time">{{ slot.startTime }} - {{ slot.endTime }}</div>
<div class="slot-price">¥{{ slot.price }} / 小时</div>
<el-button
type="primary"
size="small"
:disabled="slot.status !== 1"
@click="handleBook(slot)">
{{ slot.status === 1 ? '立即预约' : '已约满' }}
</el-button>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getAvailableSlots, createOrder } from '@/api/venue'
import { ElMessage } from 'element-plus'
const props = defineProps({
venueId: { type: Number, required: true }
})
const date = ref('')
const availableSlots = ref([])
const loadSlots = async () => {
const res = await getAvailableSlots({
venueId: props.venueId,
date: date.value
})
availableSlots.value = res.data
}
const handleBook = async (slot) => {
const res = await createOrder({
venueId: props.venueId,
slotId: slot.id,
orderDate: date.value
})
if (res.code === 200) {
ElMessage.success('预约成功,请尽快支付')
} else {
ElMessage.error(res.message || '预约失败')
}
}
onMounted(loadSlots)
</script>
这里有个细节要注意,slot列表的后端接口返回的数据里,status字段已经把被占用的时段标记出来了,前端直接根据status做渲染即可。前端不要自己再去判断时段是否被占用,这属于把业务逻辑放错了位置。
我的订单页:用el-table展示当前用户的订单列表,列包括订单编号、场地名称、预约日期、时段、金额、状态、操作。状态列用el-tag渲染,不同状态用不同颜色区分。操作列根据状态显示不同按钮:"待支付"显示"去支付"和"取消订单","已支付"显示"取消订单","已完成"显示"查看详情"。
取消订单的按钮,前端要做二次确认,用ElMessageBox.confirm弹窗提醒用户"取消后不可恢复"。这是用户体验细节,答辩时也能提一句。
5.2 管理端核心页面
管理端是体现系统完整度的重要部分。包含场地管理、时段管理、订单管理、用户管理、数据统计五个页面。
场地管理页:场地列表表格,支持新增、编辑、删除场地。新增/编辑用el-dialog弹窗,表单字段包括场地名称、类型、位置、价格、描述、图片上传。删除场地时要注意,如果该场地存在未完成订单,应该禁止删除,后端接口里要处理这个业务规则。
时段管理页:这是管理端比较有技术含量的页面。管理员需要为每个场地配置一周内的可预约时段。一个直观的方案是用横向为场地、纵向为时段的矩阵表格,单元格通过开关或按钮控制该时段是否可预约。后端提供批量保存接口:
java复制@PostMapping("/api/admin/slots/batch")
public Result<Void> batchSaveSlots(@RequestBody List<VenueSlot> slots) {
slots.forEach(slot -> {
boolean exists = venueSlotService.lambdaQuery()
.eq(VenueSlot::getVenueId, slot.getVenueId())
.eq(VenueSlot::getWeekDay, slot.getWeekDay())
.eq(VenueSlot::getStartTime, slot.getStartTime())
.exists();
// 存在则更新,不存在则插入
});
return Result.success();
}
数据统计页:用Echarts展示核心指标,比如近7天订单量折线图、场地使用率饼图、每日营收柱状图。Echarts的数据来自后端的统计接口,接口使用GROUP BY按日期分组统计。这一块在答辩时是视觉亮点,只要实现基础图表,展示效果就会比你空讲一堆技术概念好得多。
5.3 前端工程化的几个注意事项
如果你按照现在的Vue官方推荐建项目,用的是Vite创建,组件库用Element Plus,状态管理用Pinia,路由用Vue Router 4。这几个库的API和旧版有一定区别,建议一开始就按新版本学习。
在实际开发中,建议把API请求统一封装,axios实例设置baseURL和请求拦截器。所有请求自动带上token,响应拦截器统一处理401(token过期)跳转到登录页。这样后端接口变更时,只需要维护一个api文件。
js复制// request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router'
const request = axios.create({
baseURL: '/api',
timeout: 10000
})
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
request.interceptors.response.use(
response => {
return response.data
},
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('token')
router.push('/login')
}
ElMessage.error(error.response?.data?.message || '请求失败')
return Promise.reject(error)
}
)
export default request
6. 项目跑通的完整流程与联调阶段容易踩的坑
很多同学做毕设最大的障碍,不是写代码,而是"代码在自己电脑上跑不起来"。这个项目从零到能完整演示,大致需要经过以下几个步骤。
6.1 从零到跑通的环境准备
第一步是安装基础环境。JDK建议用1.8或11,MySQL用5.7或8.0,Node.js用16以上版本。这里要特别提醒,SpringBoot的版本和JDK版本之间存在兼容性问题——SpringBoot 3.x要求JDK 17以上,很多同学的电脑装的还是JDK 8,如果强行用最新版SpringBoot,启动时会直接报错。建议直接选择SpringBoot 2.7.x + JDK 8这个稳妥组合。
第二步是创建后端项目。可以用Spring Initializr在网站上生成项目包,也可以用IDEA自带的Spring Initializr创建。选择依赖时勾选Spring Web、MyBatis Framework、MySQL Driver、Validation。生成后把application.yml里配置好数据源:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/badminton?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
第三步是创建前端项目。在命令行执行npm create vue@latest,项目名可以叫badminton-web。创建完成后执行npm install,再安装element-plus和axios:
bash复制npm install element-plus axios pinia vue-router
第四步是联调阶段,需要在vite.config.js里配置代理,解决开发环境下跨域问题:
js复制export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
前期如果遇到跨域错误,很多情况下不是CORS配置问题,而是代理没配置好。Vite的代理配置好后,前端请求/api开头的接口会转发到后端8080端口,两个服务独立运行互不干扰。
6.2 联调阶段最常见的坑
- token获取不到:前端请求拦截器没有配置,或者token存储的key不一致。检查localStorage.getItem('token')和后端生成的token字段名是否一致。
- 接口返回401:前端没有在Header里带token,或者token过期、被篡改。先在后端Controller加日志打印token,再确认前端请求头是否带上了。
- 数据库时区报错:连接MySQL时url里增加serverTimezone=Asia/Shanghai即可解决,否则会出现"Server returns invalid timezone"的报错。
- 前端页面空白:多半是Vue Router模式配置问题。本地开发用createWebHashHistory更稳,打包部署再考虑history模式,不然刷新页面会出现404。
- 跨域配置了还是报错:检查一下后端是否同时配置了CORS。如果在后端全局配置了CorsFilter,开发环境代理又配了,就会出现"重复的CORS头"导致浏览器拦截。二选一即可。
6.3 答辩时老师喜欢问的几个问题
做这个项目,答辩时以下几类问题几乎必问,提前准备好能加分不少:
第一个是"订单状态是怎么流转的?"——把前面说的状态枚举和状态图讲清楚,强调取消订单时如何释放时段、退款如何处理。
第二个是"两个用户同时预约同一个场地,你的系统怎么保证不冲突?"——讲清楚UPDATE WHERE条件更新的行锁机制,说明为什么数据库层可以防超卖。
第三个是"你做了哪些安全方面的考虑?"——密码加密存储(BCrypt)、接口鉴权(JWT拦截器)、防SQL注入(MyBatis参数校验)、金额由后端计算而不是前端传入。
第四个是"如果让你继续完善,你觉得系统的瓶颈在哪里?"——可以说目前是单机部署,场地预约的并发量高时,可以考虑引入Redis分布式锁或消息队列削峰,也可以说目前支付是模拟的,后续接入微信支付/支付宝沙箱环境就是完整闭环。
7. 几点补充的实操建议
做完这个项目后,我认为有几件事是很多教程里不会讲的,但对顺利毕业非常有帮助。
第一,数据一定要造得好看。数据库里不要只有两条测试数据,建议至少造10块场地、一周的完整时段、几十个用户、上百条订单记录。订单日期覆盖过去30天和未来7天,这样你演示"查看历史订单"和"预约未来时间"时都有数据可看。统计页面的图表也会更饱满。很多同学代码写得没问题,但是因为数据太少,演示场面很空,非常吃亏。
第二,演示的时候准备一条"从注册到预约成功"的完整路径。答辩现场演示不要东点一下西点一下,按业务顺序来:注册/登录 → 查看场地 → 选择日期 → 选择时段 → 提交预约 → 模拟支付 → 查看订单 → 管理端看到该订单 → 核销完成。这一套流程走下来,老师对系统的理解完全不一样。
第三,项目源码的目录结构一定要规范干净。Controller、Service、Mapper、Entity分清楚;前端的views按用户端和管理端分文件夹;不要把node_modules提交到压缩包里,也不要留着毫无意义的Test.java文件。老师每年看几百份毕设,源码结构整洁的项目会留下很好的第一印象。
第四,把README写好。写清楚项目环境要求、数据库初始化步骤、启动步骤、默认管理员账号密码。既能帮助自己快速恢复环境,也能在提交材料时显得专业。README里的快速启动命令一定要自己实际验证过,很多同学的README写的是网上复制下来的命令,执行后根本跑不起来。
做这个项目最大的体会是:毕业设计其实不是拼算法难度,而是拼你对一个完整业务的覆盖面。技术选型主流、功能完善、能跑通、能讲清楚,就已经是良好了。如果你在场地并发控制、订单状态机、统计报表这些地方做了思考,答辩时就能超出老师的预期。希望这篇文章能帮你把羽毛球场地预约系统这个项目做得顺手,少踩几个坑。
