这个校园篮球场地管理系统,我前后做过两版。第一版用的还是老套的JSP+Servlet,后端前端糊在一起,改一个按钮都要把整个项目翻一遍。后来痛定思痛,全改成Java+Vue+SpringBoot的前后端分离架构,开发效率、代码维护、上线部署,包括拿去应付毕业设计答辩和项目面试,体验完全不一样。这篇文章就把这个项目的完整拆解写出来,从技术选型、数据库设计、前后端核心实现,到部署踩坑和面试考点,一次性讲清楚,想拿这个项目练手或者写进简历的,可以直接照着走。
1. 项目整体设计与技术选型拆解
1.1 这个系统到底要解决什么问题
校园篮球场地的预约,在很多学校里其实还是靠线下登记或者微信群接龙。场地有限,想打球的老师和学生又多,撞场、插队、占着茅坑不拉屎的情况特别普遍。管理员拿着一本纸质登记表,也没法判断哪个时段已经被预约,只能靠肉眼翻,效率特别低。
这个项目的核心价值,就是把“场地信息展示、在线预约、订单管理、管理员审核”这套流程线上化。
具体来说,系统里主要有两类角色:
- 普通用户(学生/老师):注册登录、浏览场地信息、查看场地空闲时段、提交预约申请、查看自己的预约记录、取消预约。
- 管理员:管理场地信息(新增、编辑、上下架)、处理预约订单(审核或标记完成)、发布公告、查看用户列表等。
如果你要拿这个项目做毕业设计或者个人项目,功能边界就控制在这个范围,不用再加一些花里胡哨的会员等级、积分商城,那些内容既偏离“场地管理”的核心,又会给自己挖一堆实现上的坑。
1.2 为什么是SpringBoot+Vue这套组合
先说结论:这套组合已经是当前中小型Web项目里最主流的搭配之一,也是最容易找到现成资料、遇到问题最容易搜到答案的搭配。
SpringBoot解决的问题是后端开发效率。它对Spring生态做了一层自动配置,以前需要在XML里写一大堆Bean定义,现在一个@SpringBootApplication注解加几行配置就能跑起来。内嵌Tomcat,打成一个jar包就能直接执行,部署也不用再装外置容器。对于校园场地预约这种业务逻辑不算复杂的系统,SpringBoot的快速开发优势非常明显。
Vue解决的是前端交互体验。传统JSP页面每个操作都要刷新整个页面,而Vue的组件化和响应式设计,让场地列表、预约时间选择、订单状态更新这些交互可以像桌面应用一样丝滑。Vue的学习曲线也相对平缓,会HTML、CSS、JavaScript基础的同学,看几天官方文档就能上手。
至于为什么不用更重的微服务架构或者更复杂的权限框架,道理很简单:杀鸡别用牛刀。一个校园场景的场地管理系统,单机部署、单体应用完全能扛住几千人的并发预约。架构越复杂,学习成本和部署成本越高,对课设、找工作的项目展示反而不利。
1.3 整体架构与开发流程
这个项目走的是标准的前后端分离架构,数据流向可以概括为:
浏览器(Vue页面) -> 发送HTTP请求 -> SpringBoot后端(Controller接收参数) -> Service处理业务逻辑 -> Mapper操作MySQL数据库 -> 数据原路返回 -> 前端渲染页面
从开发部署的角度,系统分成三个独立的部分,各自可以单独开发和升级:
| 部分 | 技术组件 | 职责 |
|---|---|---|
| 前端 | Vue 3 + Vue Router + Pinia + Element Plus + Axios | 页面展示、用户交互、路由控制 |
| 后端 | SpringBoot 2.7 / 3.x + MyBatis-Plus + Spring Security或JWT + Hutool | 业务逻辑、数据校验、权限认证、接口提供 |
| 数据层 | MySQL 8.x + Redis(可选) | 持久化存储、缓存热点数据 |
这里有个经验:如果只是做课设,SpringBoot用2.7版本就行,稳定、教程多,JDK8就能跑。如果是新项目或者想体现自己关注新版本,可以用SpringBoot 3.x,但注意JDK必须17以上,而且很多老版本的第三方库不兼容SpringBoot 3,遇到问题要有心理准备。
2. 数据库设计:从ER图到表结构落地
数据库设计是很多同学容易忽略但面试官一定会问的环节。我建议在写任何业务代码之前,先画一张ER图,把实体和关系理清楚。这个项目核心实体就这几个:用户、场地、预约订单、公告。
我个人画ER图不喜欢用太复杂的工具,直接用ProcessOn画实体框,标清楚主外键关系就行。最简单也最实用的规则是:一张表对应一个业务实体,表与表之间通过外键字段建立关联,核心业务表之间不要直接做多对多,用中间表拆开。
2.1 用户与角色设计细节
用户表是所有业务的基础。先看建表SQL:
CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT '密码(BCrypt加密)', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `role` tinyint NOT NULL DEFAULT '1' COMMENT '角色:0-管理员 1-普通用户', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';几个细节说明一下:
- role用tinyint而不是字符串。数据库里存整数性能好、占空间小,业务展示层再转换成“管理员/普通用户”就行。
- 密码绝对不存明文。用Spring Security自带的BCryptPasswordEncoder加密,同一个密码每次加密结果都不同,安全性高很多。
- username加唯一索引。防止注册时重复账号,这属于最基础的防御性设计。
- 时间字段统一用datetime,并且让数据库自动维护
create_time和update_time,代码里不用手动去set,减少低级错误。
2.2 场地与预约核心表
场地表相对独立,存的是场地的静态信息:
CREATE TABLE `court` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '场地名称,如一号篮球场', `location` varchar(255) DEFAULT NULL COMMENT '位置描述', `type` tinyint DEFAULT '1' COMMENT '场地类型:1-室外 2-室内', `price` decimal(10,2) DEFAULT '0.00' COMMENT '每小时价格', `image` varchar(255) DEFAULT NULL COMMENT '场地图片URL', `description` text COMMENT '场地介绍', `status` tinyint DEFAULT '1' COMMENT '状态:1-开放预约 0-停用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='篮球场地表';预约订单表是整个系统的核心,也是数据关系最复杂的一张表:
CREATE TABLE `booking_order` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_no` varchar(32) DEFAULT NULL COMMENT '订单编号', `user_id` bigint NOT NULL COMMENT '预约用户ID', `court_id` bigint NOT NULL COMMENT '场地ID', `book_date` date NOT NULL COMMENT '预约日期', `time_slot` tinyint NOT NULL COMMENT '时段编号:1-8,对应8:00-22:00每两小时一段', `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待确认 1-已确认 2-已取消 3-已完成', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_court_date_slot` (`court_id`, `book_date`, `time_slot`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约订单表';这里最关键的是一行联合唯一索引uk_court_date_slot。它的作用后面讲并发时会详细说,先记住一个原则:凡是“同一个资源同一个时间只能被一个人占用”的业务,必须用唯一索引作为兜底防线。
预约状态我用status字段维护,取值范围提前定好,代码里用常量或者枚举表示,不要魔法数字满天飞。这个状态机比较简单:
- 用户提交预约 -> 0(待确认)
- 管理员确认 -> 1(已确认)
- 用户或管理员取消 -> 2(已取消)
- 场地使用完毕 -> 3(已完成)
2.3 数据一致性与并发方案
这是我面试时最常问候选人的点:如果两个用户同时预约同一个场地同一个时段,你怎么办?
很多人的第一反应是“先用select查一下有没有记录,没有就插入”。这个方案在低并发下确实能用,但在真正并发场景下会出事。两个请求同时查都发现没有记录,同时执行插入,最终就会产生两条重复订单。
正确做法是分三层防护:
第一层是数据库唯一索引兜底。上面那张预约表已经加了court_id, book_date, time_slot联合唯一索引,就算代码逻辑全是漏洞,数据库层面最多只能插入一条,另一条会直接报DuplicateKey异常。
第二层是应用层用条件更新做原子操作。如果业务需要,比如要给场地库存扣减,可以写成:
UPDATE court SET stock = stock - 1 WHERE id = #{courtId} AND stock > 0然后判断受影响行数,如果为0说明没抢到。这种把“检查+更新”放在一条SQL里的方式,天然是原子操作,不需要额外加锁。
第三层是事务+Spring传播行为兜底。预约订单、可能的支付记录、场地状态变更,如果跨了多张表,就在Service方法上加@Transactional(rollbackFor = Exception.class),保证任何一个环节失败全部回滚。
3. 后端核心模块实现(SpringBoot)
3.1 项目脚手架与目录组织
我习惯用IDEA的Spring Initializr创建项目,选好Spring Web、MySQL驱动、MyBatis-Plus、Lombok这几个基础依赖。JDK版本如果不是刚需,用JDK8+SpringBoot 2.7的组合最稳,依赖版本不容易打架。
创建完项目后,我通常会把后端目录整理成这样的结构:
com.example.court ├── common # 通用类:统一返回结果、异常处理、常量 ├── config # 配置类:跨域、拦截器、MyBatis-Plus分页插件 ├── controller # 控制层:接收请求 ├── entity # 实体类 ├── mapper # MyBatis-Plus的Mapper接口 ├── service # 业务逻辑层 ├── util # 工具类:JWT工具、日期工具 └── CourtApplication.java很多同学习惯把Controller写得特别胖,逻辑全往里面塞。我建议遵守一个原则:**Controller只负责参数接收和结果返回,真正的业务判断全部放Service层。**这样写出来的代码既好测又好看,面试官看项目代码时也会有好印象。
application.yml里最常用的配置,直接给你一份能跑的版本:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/court_db?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 map-underscore-to-camel-case: true global-config: db-config: id-type: automap-underscore-to-camel-case一定要开,这样数据库的create_time才能自动映射成实体的createTime,少写一堆配置。MyBatis-Plus的SQL日志建议开发阶段打开,排查问题特别有用,上线前再关掉。
3.2 JWT登录认证与拦截器
登录认证这块,我建议用JWT+拦截器的方式,比直接上Spring Security更直观,也更容易给面试官讲清楚原理。
JWT的结构用一句话概括:Header.Payload.Signature,通过签名保证token内容在传输过程中没被篡改。用户登录成功后,后端生成一个包含用户ID和角色的token返回给前端,前端每次请求在请求头里带上Authorization: Bearer <token>,后端拦截器统一校验。
工具类核心方法就两个:
public class JwtUtil { private static final String SECRET = "your-secret-key"; private static final long EXPIRE = 1000 * 60 * 60 * 24 * 7; // 7天 public static String generateToken(Long userId, Integer role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }拦截器做的事有三个:从请求头取token、解析token、把用户信息放到ThreadLocal里供后续使用。注册拦截器时注意设置白名单,登录、注册、场地查询这些接口不拦截,其余接口一律要校验。
注意:SECRET不要硬编码在代码里,更不要推到Git仓库。实际开发中放到环境变量或者配置中心,项目演示阶段也要养成好习惯。
权限控制上,因为角色只有管理员和普通用户两种,可以在拦截器里解析出role后简单判断,也可以在Controller方法上用自定义@RequireAdmin注解。我个人更倾向写一个简单的注解+AOP,体现对项目设计的思考。
3.3 场地预约与防冲突下单实现
预约模块是业务逻辑最密集的地方,我直接给你看核心Service的写法:
@Override @Transactional(rollbackFor = Exception.class) public Result createOrder(BookingOrderDto dto, Long userId) { // 1. 校验场地是否存在且开放 Court court = courtMapper.selectById(dto.getCourtId()); if (court == null || court.getStatus() == 0) { return Result.error("场地不存在或未开放"); } // 2. 校验预约时间是否合法(不早于今天、时段在1-8范围内) if (dto.getBookDate().isBefore(LocalDate.now()) || dto.getTimeSlot() < 1 || dto.getTimeSlot() > 8) { return Result.error("预约时间不合法"); } // 3. 插入订单,唯一索引兜底防冲突 BookingOrder order = new BookingOrder(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setCourtId(dto.getCourtId()); order.setBookDate(dto.getBookDate()); order.setTimeSlot(dto.getTimeSlot()); order.setStatus(0); try { bookingOrderMapper.insert(order); } catch (DuplicateKeyException e) { return Result.error("该时段已被预约,请选择其他时间"); } return Result.success("预约成功,等待管理员确认"); }这段代码的关键就一个点:**把并发冲突的检测交给数据库唯一索引,代码通过捕获DuplicateKeyException转换成友好的业务提示。**如果不加这个兜底,你就要写select count(*)+insert两步操作,中间隔一个时间窗口就可能产生脏数据。
加@Transactional是为了后续业务扩展,比如扣减场地库存、发送通知,万一后面加逻辑也不至于忘记补事务。
订单编号生成我一般用:时间戳+随机数+用户ID后四位,格式如20250621143000xxxx,唯一且不用查库。
3.4 其他业务模块实现要点
场地的增删改查直接用MyBatis-Plus的IService接口就够了,不需要额外写SQL。但有一个点需要注意:管理员删除场地时,如果这个场地已经存在未完成的预约订单,会出现外键逻辑矛盾。我的做法是字段标记上下架,不做物理删除,简单又安全。
公告管理模块非常简单,一张公告表,字段就标题、内容、发布时间,前端首页展示最新一条即可,适合当作练手模块。
还有一个容易被忽略的是全局异常处理和统一返回结果。我一般会定义一个Result类,统一返回code, message, data三个字段,再写一个@RestControllerAdvice全局异常处理器,把SQL异常、参数校验异常等统一包装,前端只认这一种JSON格式,联调效率高很多。
安全提醒:SpringBoot如果开启了Actuator监控,
/actuator/heapdump等端点在某些版本下可能暴露堆内存信息,里面有密码等敏感数据。要么不引入Actuator依赖,要么在配置里只暴露health端点并且加上访问控制,这类漏洞在真实场景里可是被漏洞扫描工具重点盯着的。
4. 前端核心模块实现(Vue)
4.1 Vue环境搭建与项目创建
前端环境准备这步,问的人最多。Node.js版本别用太老的,建议16或18以上,然后用npm换一个国内镜像源,不然装依赖能装到你怀疑人生。
项目创建我建议用Vite,命令很简单:
npm create vite@latest court-web -- --template vue cd court-web npm install npm run devVite启动速度快,热更新体验好,已经是当前Vue生态的默认选择。Vue CLI那套vue create的方式虽然还能用,但官方和新项目基本都转向Vite了,别再学老教程了。
装依赖的时候,可以顺手把项目里要用的库一起装上:
npm install vue-router@4 pinia element-plus axios如果你用Element Plus,记得全量引入还是按需引入这两个选择要提前定好。课设和面试项目直接用全量引入最简单省事,在main.js里加两行就行:
import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' app.use(ElementPlus)4.2 路由配置与登录态控制
Vue Router在项目里负责页面跳转,我这里给一个简化的路由配置思路:
const routes = [ { path: '/login', component: () => import('@/views/Login.vue'), meta: { public: true } }, { path: '/', component: () => import('@/layout/Layout.vue'), redirect: '/home', children: [ { path: '/home', component: () => import('@/views/Home.vue'), meta: { title: '首页' } }, { path: '/courts', component: () => import('@/views/CourtList.vue'), meta: { title: '场地预约' } }, { path: '/orders', component: () => import('@/views/MyOrders.vue'), meta: { title: '我的预约' } }, { path: '/admin/courts', component: () => import('@/views/admin/CourtManage.vue'), meta: { title: '场地管理', role: 'admin' } }, ] } ]路由用() => import()这种懒加载写法,打包后会自动拆成多个js文件,首屏加载速度会好很多。
登录态控制靠全局前置守卫,逻辑很直白:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.public) { next() } else if (!token) { next('/login') } else if (to.meta.role === 'admin' && localStorage.getItem('role') !== '0') { next('/') } else { next() } })没有token就去登录页,有token但不是管理员就不能进管理页面。这套逻辑基本能覆盖所有权限跳转场景。
4.3 页面开发:场地预约与订单管理
场地列表页是这个项目最核心的页面,我一般用卡片布局,每个场地一张卡片,展示场地名称、位置、类型、价格和图片,加上一个“立即预约”按钮。
预约交互设计要特别注意:用户点击预约后,弹出一个Dialog,里面有两个关键选择——日期和时段。时段这里我是做成了el-radio-group,1到8一共八个时段,每个时段标签上显示“可预约”或者“已满”。
这个“可预约”状态怎么来?最简单的方式是后端提供一个接口,传入场地ID和日期,返回该日期已经被预约的时段集合,前端过滤一下就能展示。这种设计比一次性加载所有时段的可用状态更实用,数据量小、接口简单、逻辑清晰。
订单管理页面就是一张表格,用el-table展示订单编号、场地名称、预约日期、时段、状态等字段,状态用el-tag展示不同颜色,已确认是绿色、待确认是橙色、已取消是灰色、已完成是蓝色。管理员在后台订单页还能看到“确认订单”和“取消订单”的操作按钮。
整个前端逻辑总结下来就是:表单收集数据,接口提交数据,表格展示数据,状态标签区分数据。
4.4 axios封装与接口对接
前后端联调时最烦的问题是代码里到处写重复的axios请求,token要手动加,错误要挨个处理。我习惯在src/utils/request.js里做一个统一封装:
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动携带token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理错误码 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error('网络异常') return Promise.reject(error) } ) export default request统一封好之后,业务代码里一行request.get('/courts')就能发请求,token、错误处理、状态码判断全都自动完成。
开发阶段的跨域问题,不用后端配置CORS,更不要用浏览器插件,最省心的方式是走Vite的devServer代理。在vite.config.js里加:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这样前端代码里的/api/courts请求,开发环境下会被自动转发到后端8080端口,完全绕开跨域限制,前后端各做各的,互不干扰。
5. 部署、常见问题排查与面试考点总结
5.1 本地打包与服务器部署
项目做完肯定要部署上线,哪怕是部署到自己电脑上访问也是完整流程。
后端打包部署很简单:
mvn clean package -DskipTests java -jar target/court-system.jar前端打包先执行npm run build,默认会在dist目录生成一堆静态文件。如果你想本地预览打包后的效果,可以npm run preview先看看。
生产环境的部署,我推荐用Nginx做反向代理,把前端静态文件和后端接口统一成一个域名入口。Nginx配置核心就两块:
server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /var/www/court-web/dist; try_files $uri $uri/ /index.html; } # 后端接口代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files那一行特别关键,它能解决Vue Router的history模式下刷新页面404的问题。如果没有这一行,用户访问/orders页面时刷新,Nginx会找不到对应文件,直接返回404。
5.2 高频问题速查表
我整理了这个项目里最容易踩的坑,里面有不少是我自己实操时踩过的,直接做成表格方便你检索:
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 前端请求后端接口报跨域 | 前后端端口不同 | 开发环境用Vite proxy,生产环境用Nginx反向代理 |
npm install报各种依赖错误 | Node版本太高或太低 | 使用Node 16/18 LTS版本,删除node_modules和package-lock.json重装 |
| 页面点击没有反应,控制台报“Cannot read properties of undefined” | 后端返回数据结构不对,data字段层级取错 | 先用浏览器Network看接口真实返回,再调整取值 |
| 登录接口报401 | token未携带或过期 | 检查请求拦截器是否设置Authorization头 |
| Vue项目打包后打开一片空白 | base路径配置错误,资源变成绝对路径 | vite.config.js里设置base: './' |
| history模式刷新404 | Nginx没配置try_files | 按上面Nginx配置补上 |
MyBatis-Plus查询时createTime为null | 驼峰映射没开启 | application.yml配置map-underscore-to-camel-case: true |
| 端口8080被占用 | 其他进程占用了端口 | 换端口或kill -9占用的进程 |
| 连续快速点击预约,出现两条相同订单 | 并发冲突 | 数据库加联合唯一索引,代码从Bug层面解决 |
| 上传场地图片后无法显示 | 静态资源路径没映射 | 配置WebMvcConfigurer把上传目录映射成访问路径 |
5.3 面试考点梳理:从项目到八股文
做完这个项目,面试官几乎必问的几个点,提前准备绝对不吃亏:
SpringBoot方面:自动配置原理是什么?为什么加一个starter依赖就能自动创建Bean?你在项目里用了哪些starter,底层做了什么?这个项目启动流程是什么?这些属于SpringBoot面试题里最高频的内容,建议把@SpringBootApplication背后@EnableAutoConfiguration的加载逻辑背熟。
Java基础方面:项目里哪里用到Java8特性?为什么用LocalDate不用Date?@Transactional底层是动态代理实现的,你能解释吗?这里顺带会把Java动态代理、反射、异常体系一起问了,属于java面试八股文里绕不开的部分。
Vue方面:Vue的响应式原理是什么?computed和watch什么区别?路由守卫有哪些?组件通信方式有哪些?Vue面试题的高频内容基本就这些,把这个项目的代码过一遍,回答起来就有例子可以讲。
项目深挖方面:预约冲突你怎么解决?用户量大了怎么办?JWT和Session有什么区别?数据库为什么用B+树?这些问题没有标准答案,核心考察你是不是真的做过,以及有没有思考过项目的边界和瓶颈。
我建议用STAR法则来介绍项目:场景(校园场地管理混乱)、任务(实现线上预约和后台管理)、行动(选了什么技术方案、数据库怎么设计、前后端怎么分工)、结果(提效多少、解决了什么痛点)。尽量说真实数据,比如“原来人工登记需要3分钟,现在线上预约10秒完成”,这种表述比空洞的“提高了效率”有说服力得多。
5.4 项目二次扩展方向
如果时间和精力允许,这几个方向可以给项目加分:
- 对接在线支付(微信/支付宝模拟):把预约+支付整理成闭环,推荐单里加一个“待支付”状态。
- WebSocket实时通知:管理员确认订单后,用户页面能实时收到通知,不用刷新。
- Redis缓存:把场地列表、热门时段的预约情况缓存到Redis,减轻数据库压力,顺带能讲缓存穿透、击穿、雪崩这些高频面试点。
- 小程序端:Vue3的语法迁移到uni-app开发小程序成本很低,能体现跨端能力。
- 运营数据大屏:用ECharts展示每天预约量、热门场地Top5、高峰时段分析,这个对进“数据分析”相关岗位的同学很有用。
最后再分享一点个人体会。这个校园篮球场地管理系统虽然业务不复杂,但它覆盖了一个完整Web项目的全流程:需求分析、数据库设计、后端接口、前端交互、权限认证、部署上线。我当时就是用这个项目把SpringBoot和Vue的知识点真正串起来的,之前看教程总觉得看懂了,真正从零写一遍代码才发现很多细节完全没学到,比如并发冲突、跨域、代理、打包路径,每一个坑都是实打实踩出来的。
做项目的过程中,比代码更重要的是建立起的工程思维:先设计后开发、先数据库后接口、先接口后页面。这套顺序一旦养成,后面你再接触再复杂的系统,都不会慌。所以如果你正卡在“只会增删改查、不知道做什么项目”的阶段,直接拿这个系统下手就行,功能清晰、技术栈主流、扩展空间够大。