资讯动态

Spring Boot篮球竞赛预约平台:从数据模型到权限部署全解析

发布时间:2026/9/16 12:44:21 来源:尧图企业网站定制
简介基于Spring Boot的篮球竞赛预约平台毕业设计项目包面向计算机相关专业毕业生与Java后端学习者解决毕业设计选题、系统开发与论文撰写痛点。项目覆盖前台用户注册、竞赛项目浏览与预约、平台公告、个人中心以及后台登录与管理等完整功能模块可用于理解预约类系统的业务设计、权限控制与数据流转。压缩包约52.98MB内容以Java源码、毕业论文文档和演示视频为主目录结构清晰便于按模块查阅、部署与二次开发。目前已有72人学习下载。资源对系统功能进行了具体实现说明例如注册字段、赛事预约入口、个人资料更新与后台登录流程等细节均有描述能帮助读者快速还原系统原型理清Spring Boot加MySQL等典型毕业设计技术栈的落地方式兼顾学习与实践。1. 为什么这个 springboot 篮球竞赛预约平台值得你花 30 分钟拆一遍这个篮球竞赛预约平台看起来只是「赛事展示 用户预约」的 CRUD 组合但真正把它跑起来之后你会发现它覆盖了 springboot 开发中非常典型的一连串问题用户信息注册校验、赛事列表分页、预约重复提交、收藏归集以及后台管理员的角色识别。对准备用 springboot 做毕业设计的同学来说它比一个空壳的「增删改查」项目有价值得多因为它的每个模块都在回答「业务状态怎么维护」这个核心问题。项目自带源码、论文和演示视频论文里的功能描述和前台后台截图能直接对应代码很适合作为起点二次开发。我默认你至少写过基础的 Spring Boot 接口能看懂 MyBatis 或者 MyBatis Plus 的基本用法下面拆解会顺着数据、接口、权限、部署的顺序来每一节都可以直接对照源码。2. 数据模型与项目骨架springboot 预约平台的底座怎么搭拿到一个 springboot 毕设源码我习惯先不看效果图而是把数据库脚本和实体类翻一遍。预约平台的核心是「用户和赛事之间产生了哪些动作」预约和收藏这两张表就是动作的载体。表结构一旦稳定后面的接口和页面基本不会大改所以这一章先把底层结构讲透。2.1 包结构与分层项目的包路径通常放在com.example.basketball下实际包名以源码为准。整体是单体应用最经典的 controller-service-mapper 三层结构com.example.basketball ├── controller │ ├── UserController.java # 用户注册、登录、资料更新 │ ├── CompetitionController.java # 竞赛项目查询、详情 │ ├── ReservationController.java # 预约、取消、查询 │ └── AdminController.java # 后台管理相关接口 ├── service │ ├── UserService.java │ ├── CompetitionService.java │ └── ReservationService.java ├── mapper │ ├── UserMapper.java │ ├── CompetitionMapper.java │ └── ReservationMapper.java ├── entity │ ├── User.java │ ├── Competition.java │ ├── Reservation.java │ └── Favorite.java ├── config │ └── WebConfig.java # 拦截器、跨域配置 └── common ├── Result.java # 统一返回对象 └── GlobalExceptionHandler.java # 全局异常处理这样的分层能保证接口路径和业务逻辑一一对应。阅读源码时先看Result和GlobalExceptionHandler如果这两个类没有接口返回的数据格式会很难统一页面联调时就得每个接口单独写解析逻辑。看到/api/competition/list这样的路径先到 CompetitionController 里定位再到 service 找查询逻辑能省很多时间。如果源码使用了 Lombok 的 Data 注解记得在 IDEA 里安装 Lombok 插件否则实体类会报找不到 getter/setter项目启动直接失败。2.2 核心表设计用户、赛事、预约、收藏预约业务至少需要四张业务表和一张公告表。下面是按项目需求整理的核心建表语句字段名尽量和源码保持一致数据库使用 MySQLCREATE TABLE t_user ( id bigint NOT NULL AUTO_INCREMENT, account varchar(50) NOT NULL COMMENT 用户账号, password varchar(100) NOT NULL COMMENT 登录密码, user_name varchar(50) DEFAULT NULL COMMENT 用户姓名, gender varchar(10) DEFAULT NULL COMMENT 性别, age int DEFAULT NULL COMMENT 年龄, phone varchar(20) DEFAULT NULL COMMENT 用户手机, height decimal(5,2) DEFAULT NULL COMMENT 身高, weight decimal(5,2) DEFAULT NULL COMMENT 体重, avatar varchar(255) DEFAULT NULL COMMENT 头像图片, role varchar(10) DEFAULT user COMMENT 角色admin/user, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_account (account) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE t_competition ( id bigint NOT NULL AUTO_INCREMENT, comp_name varchar(100) NOT NULL COMMENT 赛事名称, comp_no varchar(50) NOT NULL COMMENT 赛事编号, category varchar(50) DEFAULT NULL COMMENT 项目分类, match_mode varchar(50) DEFAULT NULL COMMENT 比赛模式, requirement varchar(500) DEFAULT NULL COMMENT 赛事要求, match_time datetime DEFAULT NULL COMMENT 比赛时间, match_location varchar(200) DEFAULT NULL COMMENT 比赛地点, detail text COMMENT 比赛详情, cover varchar(255) DEFAULT NULL COMMENT 封面图片, status int DEFAULT 0 COMMENT 0 可约 1 满员 2 已结束, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT竞赛项目表; CREATE TABLE t_reservation ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 预约用户ID, competition_id bigint NOT NULL COMMENT 赛事ID, status int DEFAULT 0 COMMENT 0 待确认 1 已确认 2 已取消 3 已完成, remark varchar(255) DEFAULT NULL COMMENT 预约备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_competition_id (competition_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT赛事预约表; CREATE TABLE t_favorite ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, competition_id bigint NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_competition (user_id, competition_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表;这四张表的定位可以汇总成下面的对应关系表名作用关键字段t_user用户与管理员共用account, password, rolet_competition竞赛项目主数据comp_no, match_time, statust_reservation用户对赛事的预约记录user_id, competition_id, statust_favorite用户收藏赛事记录user_id, competition_id比赛时间用 datetime检索某一天赛事时会走索引用户账号加唯一约束避免同一个账号重复注册。预约表里加了一组用户和赛事的联合唯一键是为了防止同一用户对同一赛事重复提交这个约束在并发请求时尤其重要不能只靠 Service 层判断。收藏表同样用联合唯一键所以切换收藏状态时可以先尝试插入捕获重复键异常再执行删除省掉一次查询。2.3 实体映射与 MyBatis Plus 约定项目通常使用 MyBatis Plus 做 ORM实体字段和表字段默认用驼峰映射。以 Competition 为例Data TableName(t_competition) public class Competition { TableId(type IdType.AUTO) private Long id; private String compName; private String compNo; private String category; private String matchMode; private String requirement; private LocalDateTime matchTime; private String matchLocation; private String detail; private String cover; private Integer status; private LocalDateTime createTime; }TableName指定表名TableId指定主键策略matchMode自动映射到match_mode不需要额外写 resultMap。新建表时尽量用下划线命名Java 字段用驼峰这样在写QueryWrapper时列名也能直观对应。如果你看到源码里有的实体没有加TableId直接跑会报主键找不到的错误这是新手最容易踩的第一坑。另外t_reservation里不要加逻辑删除字段时把预约记录直接抹掉因为后台还要统计赛事被预约过的总次数保留历史记录比物理删除更有业务价值。3. 前台预约与收藏链路从注册、赛事列表到提交预约前台模块是用户直接面对的页面平台首页、竞赛项目、平台公告、个人中心外加注册和登录入口。这些功能背后的接口连起来就是一条完整的「浏览—预约—管理」链路。下面按调用顺序拆解每个环节都会给出一段可对照源码的代码。3.1 用户注册与登录把校验和密码加密放在哪里注册接口接收前端 POST 过来的表单字段大约有用户账号、密码、确认密码、姓名、年龄、手机、身高、体重。确认密码属于前端校验项后端只需要校验密码非空不能用它来参与重复保存。正确的后端处理方式是PostMapping(/register) public Result? register(RequestBody User user) { if (!StringUtils.hasText(user.getAccount()) || !StringUtils.hasText(user.getPassword())) { return Result.error(账号和密码不能为空); } String encryptedPassword DigestUtils.md5DigestAsHex(user.getPassword().getBytes()); user.setPassword(encryptedPassword); user.setRole(user); userService.save(user); return Result.success(); }这段代码先把明文密码转成 MD5 字符串再落库避免数据库里直接看到明文。这里用DigestUtils.md5DigestAsHex是 Spring 自带的工具类不需要额外引包。如果源码里保存的是明文密码即使只做毕业设计也建议改掉并保证登录时用同样的加密方式比对。提示登录接口不要返回密码字段即使加密过也不要返回否则会让接口被脚本刷出密文增加被撞库的风险。登录时可以先用 account 查询用户再比对密码成功后将用户对象放入 session后续接口从 session 中取当前用户。这里需要注意 login 接口不能放在拦截器拦截的路径里否则用户还没登录就无法访问登录页。3.2 竞赛项目列表分页和条件查询竞赛项目页要展示赛事名称、赛事编号、项目分类、比赛模式、赛事要求、比赛时间、比赛地点、比赛详情、封面等内容。列表接口需要支持分页和分类筛选MyBatis Plus 的 Page 对象可以少写不少代码GetMapping(/list) public ResultPageCompetition list(RequestParam(defaultValue 1) long current, RequestParam(defaultValue 6) long size, RequestParam(required false) String category) { QueryWrapperCompetition wrapper new QueryWrapper(); if (StringUtils.hasText(category)) { wrapper.eq(category, category); } wrapper.orderByDesc(match_time); PageCompetition page competitionService.page(new Page(current, size), wrapper); return Result.success(page); }参数current是页码size是每页条数前端分页组件会把这两个参数带上。category对应页面里的项目分类筛选可选可不选。排序用orderByDesc(match_time)是为了把最近的比赛放前面用户更关心接下来要开的赛事。封面的字段存的应该是图片 URL 或相对路径不要在前端直接拼 base64 字符串否则列表接口返回的数据会非常臃肿。3.3 前端页面怎么对接预约与收藏项目前端会通过异步请求调用后端接口下面是典型的预约提交代码方式是 fetch 或者项目里统一封装的 request 方法fetch(/api/reservation, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({competitionId: 123, remark: 下午有空}) }) .then(resp resp.json()) .then(data { if (data.code 200) { alert(预约成功等待管理员确认); } });这里注意请求体里没有传用户 id因为后端应该从 session 或 token 里取当前用户而不是相信前端传的 user_id 参数否则任何人都能替别人预约。收藏接口类似POST/api/favorite传 competitionId取消收藏时再调用对应的删除接口。前台的接口约定一般是这样功能请求方式路径参数赛事列表GET/api/competition/listcurrent, size, category预约赛事POST/api/reservationcompetitionId, remark取消预约PUT/api/reservation/cancelid收藏赛事POST/api/favoritecompetitionId更新个人信息PUT/api/user/update表单字段接口路径不要用动词请求方式已经说明动作路径上的命名用名词复数是前后端联调时不容易打架的约定。注意在个人中心里取消预约时id是预约记录的 id不是赛事 id很多同学在这里会把参数传错导致误取消别的预约。3.4 个人中心的信息更新与我的收藏个人中心的核心不是「改资料」而是把用户的数据聚合起来上传头像、维护身高体重、查看我的收藏。头像上传一般用MultipartFile接收文件存储到本地 upload 目录并返回访问路径查看我的收藏其实就是按用户 id 查询 t_favorite 关联 t_competition。这里有个常用优化MyBatis Plus 不支持多表 join 时直接返回 VO可以自己写一条selectSQL 放在 Mapper.xml 里也可以用Select注解写在 Mapper 接口上避免在 Service 里循环查询。如果源码里个人中心保存信息时没有重新校验用户归属建议补上。具体做法是先从 session 里拿到当前用户的 id再判断请求体里的 userId 是否一致不一致直接拒绝更新。这个点属于前后端都要注意的越权问题也是毕业设计答辩时老师很喜欢追问的地方。4. 后台角色权限与赛事管理让 springboot 平台区分用户和管理员后台登录和前台登录在代码上可以使用同一张用户表靠role字段区分。前台用户注册默认是user后台管理员账号由数据库初始化或者在写死的管理员配置中创建。真正的难点在于防止普通用户绕过菜单直接调用后台接口这一章把权限和控制逻辑讲清楚。4.1 后台角色登录与拦截器登录接口在验证账号密码后把用户对象和角色放入 Session同时注册一个拦截器拦截所有/admin/**路径Component public class AdminInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user (User) request.getSession().getAttribute(loginUser); if (user ! null admin.equals(user.getRole())) { return true; } response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write(无权限访问); return false; } }拦截器只检查请求路径所以要保证后台所有接口都放在/admin/**下如果某个后台接口写成了/api/competition/save前端页面看不到不等于安全任何人猜到路径就能调用。配置拦截器时放行登录接口和静态资源用addPathPatterns(/admin/**).excludePathPatterns(/admin/login)这样的写法。前端页面可以隐藏后台入口但后端权限必须独立判断否则就只是「看起来安全」。4.2 赛事发布与公告管理后台管理赛事包含新增、修改、删除。删除建议用逻辑删除而不是物理删除因为预约表外键引用了 t_competition。MyBatis Plus 在实体字段加TableLogic配置全局逻辑删除值即可。新增赛事时注意前端上传的封面图要设置文件大小限制spring: servlet: multipart: max-file-size: 5MB max-request-size: 20MB上面的配置把单文件限制在 5MB整个请求限制在 20MB避免用户上传超大图片占满服务器磁盘。赛事管理接口一般返回操作结果不直接返回实体。在新增赛事时要检查赛事编号是否重复数据库层需要给comp_no也加唯一索引否则两个管理员同时录入同一编号时会在最后一刻出现脏数据。4.3 预约状态机从待确认到已完成预约数据在平台里不是一成不变的正常流程是用户提交、管理员确认、比赛结束归档。参考主流预约系统的处理可以设计成四个状态状态值名称含义允许执行的动作0待确认用户提交预约等待管理员审核管理员确认或取消1已确认管理员审核通过用户取消管理员标记完成2已取消用户或管理员取消不可再变更3已完成比赛结束预约办结不可再变更状态流转逻辑放在 Service 层用常量或枚举管理。下面是一个取消预约的片段Transactional public boolean cancelReservation(Long reservationId, Long currentUserId) { Reservation r reservationService.getById(reservationId); if (!r.getUserId().equals(currentUserId)) { throw new RuntimeException(只能取消自己的预约); } if (r.getStatus() 0 || r.getStatus() 1) { r.setStatus(2); return reservationService.updateById(r); } throw new RuntimeException(当前状态不可取消); }先比较 user_id再检查状态保证任意时刻只有一个合法跳转。在两个用户同时取消同一预约的极端情况下依赖数据库行锁和 updateById 的乐观锁简单项目可以再加一个version字段实现乐观锁。状态机写清楚了后台「已确认」「已完成」按钮只是对 status 的合法修改前台用户看到的按钮也会随着状态自动变化。4.4 管理端常见的脏数据和越权问题管理端在统计预约人数时如果直接 count 预约表会把取消状态也算进去所以要写status ! 2的条件。越权问题则来自后端接口没有校验资源归属。个人中心修改资料时要先取 session 中的用户 id再判断路径里的 id 是否一致否则 A 用户传 B 的 id 就能改对方手机号。另一个常被忽略的点是后台管理员操作日志至少要记录谁在什么时间把预约状态从几改成了几否则赛事纠纷时很难排查。5. 部署排错技巧springboot 预约平台上线的最后一公里本地开发环境能跑不代表服务器能跑。这个项目是标准 springboot 工程部署前需要先检查几个点很多问题出在配置而不是代码逻辑上。5.1 application.yml 里三个容易踩的配置第一个是数据库连接要确认时区、编码和建库语句一致。推荐配置spring: datasource: url: jdbc:mysql://localhost:3306/basketball?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8第二个是端口冲突server.port在服务器上别被占用启动后看到Port 8080 was already in use就换个端口。第三个是 MyBatis Plus 的 mapper 扫描路径启动日志里出现Invalid bound statement (not found)多半是MapperScan没扫到 Mapper 接口或者 xml 的 namespace 写错。排查时先看报错信息里的类路径再对照 Mapper.java 所在的包名。5.2 打包后静态资源 404 的处理本地用 IDEA 启动正常打成 jar 包后页面样式和图片失效常见原因是资源文件没有放在src/main/resources/static下。Spring Boot 会默认映射/static目录如果你的页面资源放在了 webapp 目录打包时不会进 BOOT-INF/classes。另外如果前端通过相对路径引用 css部署到二级路径时会出现 404可以用/开头引用或者在application.yml配置server.servlet.context-path。5.3 一份可以照着打的验证清单部署完成后按顺序验证注册一个新用户修改个人资料查看竞赛项目列表提交一次预约用管理员账号登录在后台确认预约回到用户个人中心确认状态变化。期间开着浏览器的开发者工具看每一个请求的响应码和返回体。推荐最后把控制台日志级别调成 DEBUG启动后搜索 mapper 相关日志确认每条 SQL 都被正确记录这样即使预约状态异常也能直接从日志里把链路还原出来。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价