简介:这是一套面向毕业设计或课程设计的羽毛球馆管理系统完整项目,采用SpringBoot3作为后端、Vue.js3构建管理后台与用户前台,结合MySQL8实现数据管理,可帮助高校学生快速掌握前后端分离开发流程,并解决球馆预约、会员、财务等日常管理难题。资源包共6个文件,大小92.42MB,包含项目源码、配套SQL数据库脚本、需求说明文档、操作录屏以及返修源码与文档;其中文档和实际运行演示视频可协助完成环境搭建与功能验收,适合直接参考或二次开发。当前已有192人学习,内容规划较完整,从需求分析到编码实现再到部署演示均有覆盖。借助这套资源,学习者能够对照实现场馆预约、在线支付、数据报表等典型模块,节省从零设计的时间,尤其适用于需要快速产出可运行毕设项目的高校学生。
1. 羽毛球馆管理系统毕设:SpringBoot3+Vue.js3 值不值得自己搭
从 GitHub 上拉一份 SpringBoot3 + Vue.js3 的羽毛球馆管理系统源码,原以为改改 Logo 就能交差,结果本地一跑就是半天:Java 版本不对、Maven 依赖冲突、前端 node_modules 装完又报错,这种翻车在毕业设计季并不少见。羽毛球馆管理系统是典型的软工毕设选题,业务闭环完整,场地、会员、订单、统计都有得写,前后端分离架构也容易讲清楚。下面按一个能过答辩的完整项目来拆,从工程骨架、核心预订逻辑到联调避坑,讲清楚每一步怎么做、为什么这么做,适合正在选题或已选了基于 Java 的毕业设计但还没跑通第一个页面的同学。
2. 项目骨架搭建:SpringBoot3 后端与 Vue.js3 前端的最小可行工程
2.1 SpringBoot3 选型:为什么这个毕设不推荐再用 SpringBoot2
计算机毕业设计选题最怕的是选一个自己讲不透的系统,羽毛球馆管理系统的核心业务只有“预订”一条主线,但这条线上牵扯到场地状态、会员余额、订单状态和时间冲突判断,正好能覆盖数据库设计、事务、接口鉴权、前后端联调这几个评委必问的点。技术栈上,SpringBoot3 已经不是新不新的问题,而是新项目该不该用的问题。SpringBoot3 强制要求 JDK 17,很多人电脑还停在 JDK 8,所以第一步不是改代码,而是装环境。Spring Framework 6 把 javax.* 全部换成了 jakarta.*,看着只是包名变化,却让大量老代码直接失去兼容性,这也是 GitHub 上很多旧项目在新环境下一跑就报错的原因。
从答辩角度讲,基于 Java 的毕业设计选题能说清楚“为什么用 SpringBoot3 而不用 SpringBoot2”本身就是加分项。你可以从官方支持周期说起,SpringBoot2 的 OSS 支持已经结束,新项目再用旧版本后续安全问题没人维护;也可以从 Jakarta 迁移讲 Spring 生态向前走的必然性。到了 2025 年,MyBatis-Plus、SpringDoc、Sa-Token 这些常用库都出了配套 SpringBoot3 的 starter,不用像前两年那样自己改源码。这个题目的主流技术路线是 SpringBoot3 + MyBatis-Plus + MySQL + Vue3 + Vite + Element Plus,下面按这条线把骨架搭起来。
2.2 后端初始化:Spring Initializr 依赖清单与关键 pom 配置
我一般直接去 Spring Initializr 官网生成基础工程,不推荐自己在 Maven 里手搭骨架,漏依赖是最常见的翻车原因。页面左侧选 Maven、Java、Spring Boot 3.x,JDK 选 17,依赖勾选 Spring Web、Validation、MySQL Driver、Lombok。注意 Initializr 里没有 MyBatis-Plus 和 SpringDoc,这两个需要手动写进 pom,否则后面启动会卡在数据源配置。下面是 pom.xml 里需要重点确认的部分:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <!-- 版本以你在 Initializr 生成时选择的 3.x 稳定版为准 --> <version>3.3.x</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- SpringBoot3 必须用带 spring-boot3 标识的 starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <!-- 到 Maven 仓库选最新稳定版,不要用 2.x 老版本 --> <version>3.5.x</version> </dependency> <!-- 接口文档用 springdoc,springfox 在 SpringBoot3 下起不来 --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <!-- 同样选最新稳定版 --> <version>2.x</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这里最关键的三个参数:JDK 版本必须是 17 以上,MyBatis-Plus 必须用mybatis-plus-spring-boot3-starter,MySQL 驱动坐标是com.mysql:mysql-connector-j。很多毕业设计从 GitHub 抄来的老项目用的还是mybatis-plus-boot-starter,那是给 SpringBoot2 用的,放进 SpringBoot3 工程里启动直接报类冲突。spring-boot-starter-validation不要省,后面做预订参数校验时会用到@Valid注解。
application.yml 里的数据源配置同样要跟上版本变化。driver-class-name 写成com.mysql.cj.jdbc.Driver,URL 建议带上serverTimezone=Asia/Shanghai参数,否则 MySQL 时区不一致会导致 LocalDateTime 字段偏差一小时,这种问题排查起来特别费时间。url 格式为:
spring: datasource: url: jdbc:mysql://localhost:3306/badminton?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver2.3 前端初始化:Vite + Vue3 + Element Plus,5 分钟跑起页面
前端不建议用 Vue CLI 的 webpack 方案,Vite 启动速度快,而且 Vue 官方脚手架默认就是 Vite。在项目根目录外执行:
npm create vite@latest badminton-admin -- --template vue cd badminton-admin npm install npm install element-plus axios pinia npm run dev--template vue生成的是 Vue3 组合式写法的基础工程。装 Element Plus 用来做管理后台的表格、表单和弹窗,axios 负责发请求,pinia 做登录状态管理,这三个是毕设项目的标配。跑起来之后访问 5173 端口,看到 Vite 默认页就说明前端环境通了。
main.js 里需要注册 Element Plus,否则组件全部不渲染:
import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' import router from './router' const app = createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount('#app')如果担心全量引入 Element Plus 让首屏加载变慢,可以换成按需引入的unplugin-vue-components插件方案,但毕设项目页面不多、数据量不大,全量引入是性价比最高的选择。答辩被问到优化思路时,你能说出按需引入和全量引入的差异就够了,不必真去实现。到这里前后端骨架都立住了,下一步开始设计业务表。
3. 核心业务建模:场地、会员与订单的建表设计和预订冲突检测
3.1 三张核心表:字段怎么设计才能让接口少踩坑
羽毛球馆管理系统的数据模型不需要设计得很复杂,常见做法是场地表、会员表、订单表三张起步,再加一张操作日志表用于管理员审计。我建议把订单表作为核心表来反推其他表字段:一次预订需要知道哪个会员、订了哪个场地、哪天、几点到几点、付了多少钱、当前状态是什么。所有字段都围绕这条主链路展开,不要一上来就加教练表、器材表,先把核心闭环跑通。
建表脚本可以直接在 MySQL 里执行,注意订单表名避开order这个 MySQL 保留字,我习惯统一加sp_前缀:
CREATE TABLE sp_venue ( id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '场地ID', name varchar(50) NOT NULL COMMENT '场地名称,如1号场', venue_type tinyint NOT NULL DEFAULT 1 COMMENT '1羽毛球 2乒乓球', price_per_hour decimal(10,2) NOT NULL COMMENT '每小时价格', status tinyint NOT NULL DEFAULT 1 COMMENT '1可用 0停用', create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ) COMMENT '场地表'; CREATE TABLE sp_member ( id bigint PRIMARY KEY AUTO_INCREMENT, username varchar(32) NOT NULL UNIQUE COMMENT '登录名', password varchar(128) NOT NULL COMMENT 'BCrypt哈希后的密码', phone varchar(20), balance decimal(10,2) NOT NULL DEFAULT 0 COMMENT '账户余额', status tinyint NOT NULL DEFAULT 1 COMMENT '1正常 0冻结', create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ) COMMENT '会员表'; CREATE TABLE sp_order ( id bigint PRIMARY KEY AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT '订单号', member_id bigint NOT NULL COMMENT '会员ID', venue_id bigint NOT NULL COMMENT '场地ID', book_date date NOT NULL COMMENT '预订日期', start_time time NOT NULL COMMENT '开始时间', end_time time NOT NULL, total_price decimal(10,2) NOT NULL COMMENT '总金额', status tinyint NOT NULL DEFAULT 1 COMMENT '1待支付 2已支付 3已完成 0已取消', create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_member_venue_time (member_id, venue_id, book_date, start_time) ) COMMENT '预订订单表';订单表加唯一索引uk_member_venue_time很有必要,它能在数据库层拦截同一会员在同一场地同一开始时间的重复提交。密码字段必须存 BCrypt 哈希,不能明文,评委问安全问题的时候这是一个很扎实的回答点。场地价格用decimal(10,2)而不是float,金额计算最怕浮点误差。预订时间拆成book_date、start_time、end_time三个字段而不是一个 datetime 区间,是因为场地的可预订单位是小时段,按日期加时间分别查询更高效,索引也能命中。
3.2 预订接口:时间冲突检测的 SQL 条件与事务控制
预订接口是这个系统最核心的业务逻辑,要干四件事:校验场地状态、检查时间段是否冲突、计算金额扣余额、生成订单。时间冲突检测不能只在 Java 里用循环比对,正确做法是让 MySQL 用一条条件判断完成。核心冲突条件是:现有订单时间段为[start_time, end_time),新请求时间段为[s, e),只要start_time < e AND end_time > s,就说明两个区间交叠。
对应到 MyBatis-Plus 的写法:
@Transactional(rollbackFor = Exception.class) public OrderVO createOrder(BookRequest req) { Venue venue = venueMapper.selectById(req.getVenueId()); if (venue == null || venue.getStatus() != 1) { throw new BizException("场地不存在或已停用"); } LocalTime start = LocalTime.parse(req.getStartTime()); LocalTime end = LocalTime.parse(req.getEndTime()); if (!start.isBefore(end)) { throw new BizException("开始时间必须早于结束时间"); } Long count = orderMapper.selectCount( new LambdaQueryWrapper<Order>() .eq(Order::getVenueId, req.getVenueId()) .eq(Order::getBookDate, req.getBookDate()) .in(Order::getStatus, Arrays.asList(1, 2)) .apply("start_time < {0} AND end_time > {1}", end, start) ); if (count > 0) { throw new BizException("该时段已被预订,请换一个时间段"); } BigDecimal amount = venue.getPricePerHour() .multiply(BigDecimal.valueOf(Duration.between(start, end).toMinutes() / 60.0)); Member member = memberMapper.selectById(req.getMemberId()); if (member.getBalance().compareTo(amount) < 0) { throw new BizException("余额不足"); } member.setBalance(member.getBalance().subtract(amount)); memberMapper.updateById(member); Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setMemberId(req.getMemberId()); order.setVenueId(req.getVenueId()); order.setBookDate(req.getBookDate()); order.setStartTime(start); order.setEndTime(end); order.setTotalPrice(amount); order.setStatus(1); orderMapper.insert(order); return convertToVO(order); }参数说明:@Transactional让扣余额和插入订单在同一事务中,中途抛异常自动回滚,不会出现钱扣了订单没生成的脏数据。冲突检测里.in(Order::getStatus, Arrays.asList(1, 2))表示待支付和已支付的订单都算占用场地,已取消和已完成的订单不参与冲突判断,这个细节经常被漏掉,漏掉之后会出现取消的时段无法再被预订。.apply里的{0}和{1}是参数占位符,框架会做预编译,避免拼接 SQL 的注入风险。
金额计算用Duration.between算出分钟数再除以 60,得到小时数。这里要注意除法尽量用BigDecimal而不要用double,羽毛球场地价格一般是整数元每小时,但涉及折扣或者按半小时计费时浮点误差会很明显。
3.3 订单状态机:待支付、已支付、取消与余额回滚
订单状态不能只用一个字段随意改,要给状态转换设边界。我定义四个状态:0 已取消、1 待支付、2 已支付、3 已完成。创建订单后状态是待支付,因为前面代码里已经扣了会员余额,所以这里把“待支付”理解为“已扣款但还没核销”,这跟普通电商先下单后付款的流程不一样,答辩时要把自己的设计逻辑讲清楚。
状态流转的合法路径是:待支付可以取消,取消后余额回滚;待支付可以变成已支付;已支付只能变成已完成;已取消和已完成是终态,不能再动。用代码表达就是:
public void cancelOrder(Long orderId, Long memberId) { Order order = orderMapper.selectById(orderId); if (order == null || !order.getMemberId().equals(memberId)) { throw new BizException("订单不存在"); } if (order.getStatus() != 1) { throw new BizException("当前状态不允许取消"); } Member member = memberMapper.selectById(memberId); member.setBalance(member.getBalance().add(order.getTotalPrice())); memberMapper.updateById(member); order.setStatus(0); orderMapper.updateById(order); }这段代码的关键是取消前先判断状态,只有待支付订单能取消。如果允许已支付订单直接取消,就会产生一个漏洞:场地已经核销但钱退了,场馆方会亏。毕业设计真要做完整,可以加“开场前 2 小时不允许取消”的规则,把限制条件放在状态判断前面。余额回滚和订单状态更新必须在同一个事务里,否则取消成功后余额没加回去,用户就来投诉了。
4. 前后端联调与登录认证:JWT 鉴权和 CORS 的完整配置
4.1 登录签发 JWT 与拦截器校验的最小实现
管理后台的场地维护、会员管理、订单查询都需要登录后才能访问,前后端分离项目里最常用的方案是 JWT。登录接口校验用户名密码成功后,生成一个带过期时间的 token 返回给前端;前端把 token 存到 localStorage;后续每次请求在 Authorization 头带上;后端用拦截器解析 token,解析失败就返回 401。
JWT 工具类可以自己写,依赖用io.jsonwebtoken:jjwt。核心方法很简洁:
public class JwtUtil { private static final SecretKey KEY = Keys.hmacShaKeyFor( "badminton-bishe-secret-key-2025-springboot-vue3".getBytes(StandardCharsets.UTF_8)); public static String createToken(Long userId, String username) { return Jwts.builder() .subject(String.valueOf(userId)) .claim("username", username) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000)) .signWith(KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().verifyWith(KEY).build() .parseSignedClaims(token).getPayload(); } }参数说明:hmacShaKeyFor要求的密钥长度至少 32 字节,短了会抛异常,这也是新手容易踩的坑。过期时间设 1 小时,毕设项目不需要做 Refresh Token,答辩被问的时候答“简化了刷新机制,生产环境建议加 Redis 存储”即可。拦截器里先放行登录接口,再对 /api/** 下的其他接口做 token 校验,注意要把 OPTIONS 请求直接放行,否则预检请求过不来,这是第四张重点讲的问题。
4.2 跨域配置:CORS 映射和 Vite 代理两条路
前端跑在 5173 端口,后端跑在 8080 端口,端口不同就构成跨域。常见做法是在后端写一个全局 CORS 配置类,继承 WebMvcConfigurer:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }关键参数是allowedOriginPatterns("*")和allowCredentials(true)必须成对出现。SpringBoot 的 CORS 规范里,allowedOrigins("*")和allowCredentials(true)同时配置会直接报错,因为浏览器不允许带凭证的请求使用通配符来源。allowedOriginPatterns就是为了解决这个问题设计的,它允许在开启凭证的前提下匹配任意来源。maxAge(3600)是让浏览器缓存预检结果 1 小时,减少 OPTIONS 请求次数。
开发阶段也可以不走 CORS,直接在前端 vite.config.js 里配置代理:
export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })配置代理之后,前端请求写/api/xxx,Vite 会把请求转发到 8080 端口的后端,浏览器看到的是同源请求,跨域配置甚至可以不用写。我的习惯是两条都配上,代理用于开发,CORS 配置留给线上部署,这样切换环境不用改代码。
4.3 前端 Axios 封装:token 注入和 401 自动跳转
不封装 axios 的话,每个页面都要手写axios.get(url, { headers: { Authorization: ... } }),六十个接口下来全是重复代码。封装一个统一的 request 实例是前端改造最重要的步骤。在 src/utils 下创建 request.js:
import axios from 'axios' 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 => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } ) export default request请求拦截器做的事很简单:每次发请求前从 localStorage 取 token,有就放进 Authorization 头。响应拦截器处理 401,token 过期或者未登录时清理本地 token 并跳回登录页。response => response.data这行是统一解包,后端返回体通常包一层{ code, message, data },解包后页面里拿到的直接就是业务数据,不用每处再写.data.data。注意window.location.href = '/login'用硬跳转而不是路由跳转,是为了强制刷新,把内存里可能残留的管理员状态全部清掉。
5. 常见问题与避坑:SpringBoot3+Vue.js3 组合最容易翻车的 5 个地方
5.1 启动失败:MyBatis-Plus 依赖坐标用错
现象:SpringBoot3 项目一启动就报Failed to configure a DataSource: 'url' attribute is not specified,或者NoSuchMethodError,按照网上老教程改配置仍然报错。 原因:网上大量教程和 GitHub 项目都是 SpringBoot2 时代写的,依赖坐标挂在com.baomidou:mybatis-plus-boot-starter上。这个 starter 内部引用的还是javax.*包名,SpringBoot3 强制jakarta.*,类加载直接冲突。MySQL 驱动同理,旧坐标mysql:mysql-connector-java已经废弃。 解决:换成com.baomidou:mybatis-plus-spring-boot3-starter,版本选最新稳定版;MySQL 驱动用com.mysql:mysql-connector-j。改完 pom 后执行mvn clean再重新导入,避免本地仓库残留旧依赖。
5.2 Swagger 文档打不开:springfox 在 SpringBoot3 下无解
现象:pom 里加了springfox-swagger2和springfox-swagger-ui,启动时报Failed to start bean 'documentationPluginsBootstrapper',或者页面空白。 原因:springfox 3.x 是基于 Spring Framework 5 开发的,对jakarta命名空间没有适配。SpringBoot3 里 WebMvc 路径匹配策略变了,springfox 内部的插件处理器摸不到对应组件,基本属于不可修复的兼容问题。 解决:弃用 springfox,改用springdoc-openapi-starter-webmvc-ui,它原生支持 SpringBoot3。工厂后访问地址不是老的/swagger-ui.html,而是/swagger-ui/index.html。实体类上的@ApiModel注解改成@Schema,接口注解@ApiOperation改成@Operation,改动量不大。
5.3 跨域配置写了还是 Access-Control-Allow-Origin 报错
现象:后端明明写了addCorsMappings,前端请求仍然报跨域错误,浏览器 Network 面板里能看到请求已经发出,响应头里却没有 CORS 字段。 原因:如果项目里加了登录拦截器,拦截器默认执行顺序在 CORS 处理器之前。请求进入拦截器时被 token 校验拦住,根本走不到 CORS 配置那一步,同时 OPTIONS 预检请求没有 token,被直接拦截返回了非 200 状态。 解决:在拦截器 WebMvcConfigurer 里给addInterceptors注册的拦截器调用.order(0),或者简单粗暴地让拦截器放行 OPTIONS 请求。另一个建议是开发阶段直接用 Vite 代理,代理模式下浏览器认为所有请求同源,CORS 问题完全绕开。
5.4 LocalDateTime 返回一串数字或 T 格式
现象:接口返回的预订时间是2025-05-20T10:00:00,更严重的是一串类似[2025,5,20,10,0]的数组,前端时间组件直接解析失败。 原因:SpringBoot3 默认用 Jackson 序列化,LocalDateTime默认输出 ISO 格式即带 T 的字符串。很多人只在 application.yml 里配了spring.jackson.date-format,这个配置只对java.util.Date生效,对LocalDateTime完全无效。 解决:最简单的是在时间字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss");全局做法是定义一个Jackson2ObjectMapperBuilderCustomizer,统一配置LocalDateTime的序列化格式。注意 SpringBoot 自动携带 jsr310 模块,不需要额外引入依赖。
5.5 前端打包后静态资源 404
现象:npm run build生成了 dist 目录,把里面文件拷贝到后端的src/main/resources/static后重新打包,java -jar启动接口能通,但访问首页 404。 原因:两个问题叠加。第一,Vite 默认资源路径是绝对路径/assets/xxx,打包产物直接放在后端静态目录下路径不匹配;第二,Spring Boot 对 Vue Router 的 history 模式没有做 fallback,刷新页面时后端不知道把请求转发给 index.html。 解决:部署用 nginx 是最稳的,把 dist 目录作为网站根目录,配置location /api反向代理到后端 8080。如果只做演示,在 vite.config.js 里设置base: './',同时后端写一个处理所有非 /api 路径的 Controller,转发到 index.html。我的经验是诉求“能跑给老师看”用后者,诉求“能上线运行”用 nginx。
6. 验证与答辩:用接口测试和演示顺序给毕设收尾
6.1 用 curl 脚本过一遍核心接口
系统写完别急着打开页面手工点,先用 curl 把核心链路过一遍,确认后端逻辑没有大问题再动前端。下面这段脚本可以用在验收和自测:
# 登录获取 token TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}' | jq -r '.data.token') # 创建预订 curl -s -X POST http://localhost:8080/api/orders \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"venueId":1,"bookDate":"2025-05-20","startTime":"10:00","endTime":"11:00"}' # 重复提交,预期返回时段冲突 curl -s -X POST http://localhost:8080/api/orders \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"venueId":1,"bookDate":"2025-05-20","startTime":"10:30","endTime":"11:30"}'第二段应该返回“该时段已被预订”,这验证了时间冲突检测的有效性。有这个输出,答辩时展示给评委看非常有说服力。
6.2 答辩演示顺序与一个真实翻车教训
演示顺序建议按业务闭环来:管理员登录,维护场地和价格;新会员注册;登录系统;选择场地和时间段提交预订;查看订单列表;取消订单;看余额变化。最后演示 Swagger 文档,证明接口规范。
当年我做软工毕设时,订单表没加唯一索引,现场演示连点两次预订按钮生成了两条重复订单,被评委一眼看出来。后来靠数据库唯一索引加前端按钮 loading 置灰双重解决。所以答辩前别只跑通流程,多点几次、多发几次请求,让学生在演示时找不到破绽,这才是最稳妥的验收方式。希望帮到你。
本文还有配套的精品资源,点击获取