ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Uniapp与SpringBoot的高校运动会报名系统实战解析

基于Uniapp与SpringBoot的高校运动会报名系统实战解析 刚做完一套“基于小程序的高校运动会报名系统”选了 Vue Uniapp SpringBoot 的组合前后端加小程序端一把梭从立项到上线跑通给学院用踩了不少坑也攒下一些可以复用的经验。如果你的需求也是“学生用微信小程序报名 管理员后台统一管理 后端接口支撑”那这套东西的完整思路、表结构设计、核心实现和排坑清单下文基本都能给你参考。1. 为什么用这个技术栈选型逻辑与整体架构拆解1.1 三端一体的核心思路大学运动会的报名场景其实很典型学生要能快速浏览比赛项目、提交报名、查看审核结果体育部老师要能创建赛事、设置名额、审批报名、录入成绩。传统线下填表或者用 Excel 收集一到报名高峰期就乱套班级汇总、项目限报、性别组别限制这些问题全靠人工记基本就是灾难现场。我当时定的方案是三端一体学生端微信小程序用 Uniapp 构建。为什么不用微信原生因为团队里后续还可能有校内 App、H5 的诉求Uniapp 一套代码多端跑少维护一套。管理端Vue Element Plus 的后台管理系统给体育部老师用负责赛事配置、报名审核、成绩录入。后端SpringBoot MyBatis-Plus MySQL统一走 RESTful API 向外输出能力。三端共用同一套 API小程序端和管理后台操作的是同一份数据这就避免了之前那种“学生报一个表、老师自己再统计一遍”的重复劳动。整个系统跑起来之后核心价值就是把报名流程从“人追人”变成“系统通知人”状态流转清清楚楚。1.2 技术选型避坑为什么不用纯微信原生我知道有人会说就一个微信小程序用原生写不是更直接如果只做一个报名页面原生确实够了。但运动会系统的边界没这么小它要管赛事项目、分组、名额限制、审核流、成绩排行后续可能还要接消息模板提醒。如果原生和 Vue 生态来回切开发心智负担很重。Uniapp 的另一个好处是它对 Vue 语法非常友好如果你已经会 Vue那 Uniapp 的上手成本非常低。我当时把页面拆成页面级组件和通用组件两层表单页、列表页、详情页全部用结构相近的方式去写后边加功能的时候维护成本明显低。SpringBoot 这边就不用啰嗦了Java 生态成熟校内这类管理系统很多都是 Java 系的人接手。我用的是 SpringBoot 2.7.x MyBatis-Plus MySQL 8.0接口层配合统一的返回体 Result 和全局异常处理器前端拿到数据的格式永远是稳定的联调的时候少很多撕扯。1.3 系统功能模块划分整体功能可以拆成五个模块这里直接列功能清单赛事管理模块创建运动会/比赛项目、设置组别男子组、女子组、混合组、设置报名起止时间、设置人数上限和最低人数。报名管理模块学生浏览可报项目提交报名、取消报名老师端审核报名可通过或驳回超过人数上限时自动拦截。成绩管理模块老师录入成绩、排名自动生成学生端可查看个人成绩和项目排名。个人中心模块学生的报名记录、审核状态、成绩通知全部聚合展示。系统管理模块学院/班级信息维护、用户管理、数据统计导出。模块划分的关键是要想清楚角色边界。学生只能看和报名老师只能管理和审核超级管理员管全站。这个权限模型在做接口设计时就要定下来否则后边加权限容易改成一坨。2. 数据库设计与核心表结构2.1 用户、比赛项目、报名的关系建模这系统里最核心的关系就是用户报名比赛项目。多对多的关系中间表就是报名记录表。但如果只是建一张关联表后边成绩、审核状态、取消记录都会没地方放所以我做了更细的表设计。先看用户表我在设计时直接把微信小程序用户的 openid 作为唯一标识同时冗余了学院、班级、学号、性别字段。性别这个字段很关键因为比赛分组经常用到用户注册时根据学号信息自动带出来避免报名时再填。比赛项目表要拆成“赛事”和“项目组别”两层。比赛是第几届校运动会项目是百米、跳远这种具体的小项。每个项目组别下有性别限制、名额限制、报名开始和结束时间。拆开之后如果明年办第二届直接复用“赛事”模板项目组别不用重建比较灵活。报名表是重头戏字段包括报名 ID、用户 ID、项目 ID、状态、报名时间、审核时间、备注。我把报名状态设计成已报名、待审核、已通过、已驳回、已取消、已退赛。虽然有的场景不需要审核只要有报名截止时间就够了但学校这类场景还是需要体育部老师人工把关比如某些高水平运动员只能报特定项目所以状态字段多几个状态位以后也好扩展。2.2 关键表设计说明下面这四张表的 DDL 是我实际项目中用到的也做了一定简化你拿去可以少走弯路。CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, openid VARCHAR(64) NOT NULL UNIQUE COMMENT 微信openid, name VARCHAR(32) COMMENT 姓名, student_no VARCHAR(20) COMMENT 学号, college_id BIGINT COMMENT 学院id, class_name VARCHAR(64) COMMENT 班级名称, gender TINYINT COMMENT 1男 2女, role TINYINT DEFAULT 1 COMMENT 1学生 2管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );CREATE TABLE competition ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 赛事id, title VARCHAR(128) COMMENT 赛事名称, year VARCHAR(16) COMMENT 届数如2024, status TINYINT DEFAULT 1 COMMENT 0未开始 1报名中 2已截止 3已结束, start_time DATETIME COMMENT 报名开始时间, end_time DATETIME COMMENT 报名截止时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE event_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 项目id, comp_id BIGINT COMMENT 关联赛事id, item_name VARCHAR(64) COMMENT 项目名称, category VARCHAR(32) COMMENT 组别男子组/女子组/混合组, limit_count INT DEFAULT 0 COMMENT 报名人数上限, current_count INT DEFAULT 0 COMMENT 当前报名人数, is_team TINYINT DEFAULT 0 COMMENT 是否团体项目, status TINYINT DEFAULT 1 COMMENT 0关闭 1开启 );CREATE TABLE registration ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 报名id, user_id BIGINT COMMENT 用户id, event_item_id BIGINT COMMENT 项目id, status TINYINT DEFAULT 0 COMMENT 0待审核 1已通过 2已驳回 3已取消 4已退赛, remark VARCHAR(255) COMMENT 备注, apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, audit_time DATETIME COMMENT 审核时间 );这里有个细节我在 event_item 表里加了 current_count 字段是为了报名列表展示时快速判断有没有报满不用每次 count(*) 。代价是每次报名成功和取消时要同步更新这个字段。性能换一致性的取舍在校园这种量级下完全够用而且实现简单。2.3 状态机设计报名、审核、成绩这套系统最需要理清楚的是状态流转否则后边代码会写得东一块西一块。报名状态我按下面的流转来约束学生提交报名 - 状态变为 0待审核老师通过 - 1驳回 - 2并且驳回时填 remark 说明原因学生在待审核状态下可以取消 - 3学生报名通过后如果因为受伤等原因不能参赛可以申请退赛老师确认后 - 4成绩这一块我单独建了一张成绩表关联报名 ID 而不是直接挂在用户和项目上。为什么因为一场比赛下来可能有资格赛、决赛、复赛等多个轮次。成绩表设计成可以存多个轮次的成绩最后取最优成绩或决赛成绩排名。CREATE TABLE sports_result ( id BIGINT PRIMARY KEY AUTO_INCREMENT, registration_id BIGINT COMMENT 关联报名id, round_no TINYINT DEFAULT 1 COMMENT 第几轮/预决赛标识, score VARCHAR(32) COMMENT 成绩如12.15秒/5.62米, ranking INT COMMENT 本轮排名, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );状态机的好处是前端页面可以根据状态值直接渲染不同按钮。比如待审核时显示“取消报名”已通过后显示“申请退赛”已驳回显示“查看原因”。后端接口也可以只允许指定状态跳转到指定状态很大程度上避免脏数据。3. 后端 SpringBoot 接口实现要点3.1 项目初始化与依赖配置SpringBoot 项目初始化没什么神秘直接 Spring Initializr 生成Java 8 或者 11 都行。我这边用的是 Java 8 SpringBoot 2.7.14因为学校服务器上 JDK8 最常见而且和 MyBatis-Plus 的兼容性最稳。pom.xml 里核心依赖只需要这几类dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency配置上有一个容易踩的坑MySQL 8.0 的连接驱动要写成 com.mysql.cj.jdbc.Driver并且时区参数一定要加上 serverTimezoneAsia/Shanghai否则接口返回的时间比本地时间早 8 小时排查半天都找不到原因。spring: datasource: url: jdbc:mysql://localhost:3306/sports?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassMyBatis-Plus 这边我习惯开启驼峰映射和逻辑删除这样数据库字段用下划线实体类用驼峰代码写起来非常顺。逻辑删除字段我通常叫 deleted省得物理删数据后边没法追溯。3.2 报名接口的并发控制报名接口是系统里最容易出并发问题的地方。比如一个项目限报 30 人第 31 个学生同时点报名如果你只是先查后插那极有可能超报。我在实际开发中用了两种手段兜底第一种是数据库层加唯一索引。比如一个学生对于同一个项目组别只能有一条有效报名记录那就在 registration 表上加联合唯一索引 (user_id, event_item_id) 但要注意唯一索引没法约束“不同用户同一项目”的总数。第二种是 Redis 分布式锁。报名接口进来后以 event_item_id 为 key 加锁锁住之后再去判断 current_count 是否已经达到 limit_count没满就插入记录并更新 current_count满了直接返回“该项目的报名人数已满”。这样即使多个人同时请求也不会超报。代码大概长这样public Result createRegistration(RegistrationDTO dto) { Long itemId dto.getEventItemId(); String lockKey reg:lock: itemId; boolean locked RedisUtils.tryLock(lockKey, 3, TimeUnit.SECONDS); if (!locked) { return Result.error(系统繁忙请重试); } try { EventItem item eventItemMapper.selectById(itemId); if (item null || item.getStatus() ! 1) { return Result.error(项目不存在或已关闭); } if (item.getCurrentCount() item.getLimitCount()) { return Result.error(该项目报名人数已满); } // 插入报名记录状态为待审核 registrationMapper.insert(...); // 更新当前报名人数 eventItemMapper.increaseCount(itemId); return Result.success(); } finally { RedisUtils.unlock(lockKey); } }关于 Redis 锁校园系统玩不出太复杂的分布式但引入后确实能挡住多数并发问题。如果你们那边没有 Redis也可以用 synchronized 或者数据库行锁但分布式锁是最接近规范实践的。3.3 微信登录与鉴权小程序端的用户登录不能自己搞一套账号密码学生也不乐意输。我们走的是 wx.login 拿到 code然后后端用 code 调微信的 openid 接口拿到 openid 后查库没有就注册新用户。这个过程注意两点第一code 是一次性的后端拿到之后短时间要用掉而且要缓存 openid 对应的 token避免每次进小程序都重复走微信接口。我这边设计是登录成功后返回一个自定义 token前端存到 storage后续所有请求在 header 里带 token后端用拦截器解析出用户身份。第二微信小程序的 appid 和 secret 千万不要写死在代码里更不能提交到 Git。我习惯放在 application.yml 里然后环境变量覆盖或者用 jasypt 加密配置。热词里提到“springboot yml密文”就是这类场景。用 jasypt 只做成本很低但好习惯能避免上线后密钥泄露。RestController RequestMapping(/api/auth) public class AuthController { PostMapping(/wx-login) public Result login(RequestBody WxLoginDTO dto) { String code dto.getCode(); WxSession session WxUtils.code2Session(code); SysUser user userMapper.selectByOpenid(session.getOpenid()); if (user null) { user registerNewUser(session.getOpenid(), dto); } String token createAndCacheToken(user); return Result.success(token); } }4. 前端 Uniapp 小程序开发实录4.1 项目搭建与环境配置Uniapp 项目直接用 HBuilderX 创建或者 Vue CLI 创建都行。我推荐用 vue3vite 版本跑起来之后开发、构建、上传到微信开发者工具都很顺。新建项目后最要紧的是改 manifest.json。小程序 appid 要换成实际的否则真机预览和上传都会报错。项目里如果用到自定义导航栏或者 tabBar也要在 pages.json 里提前配好。我这边为了保持页面整洁首页和报名页用的是自定义导航栏这样状态栏适配起来更灵活。开发阶段我推荐直接“运行到微信开发者工具”这样能实时看到 API 调用请求。要注意一点如果你在小程序开发者工具里勾选了“不校验合法域名”本地 dev 环境可以不走 HTTPS但真机预览的时候必须把后端地址改成已配置的 HTTPS 域名否则请求全部白屏。4.2 报名页面的核心实现报名页是学生使用频率最高的页面。我先在首页把可报名的项目列表拉下来每个项目卡片显示项目名称、组别、剩余名额、报名状态。点击进入详情页后展示报名表单这里有一个细节在某些团体项目和跨学院项目中需要学生额外填写组队成员学号不能只提交自己。详情页核心代码大概是这样template view classdetail-wrapper van-panel :titleitem.itemName view classmeta组别{{ item.category }}/view view classmeta剩余名额{{ item.limitCount - item.currentCount }}/view view classmeta报名截止{{ formatTime(item.endTime) }}/view block v-ifitem.isTeam 1 input v-modelteamMemberNo placeholder填写队员学号多个用逗号分隔 / /block van-button typeprimary block clicksubmitApply报名/van-button /van-panel /view /template script import { applyEvent } from /api/event export default { data() { return { item: {}, teamMemberNo: } }, methods: { async submitApply() { const res await applyEvent({ eventItemId: this.item.id, teamMemberNo: this.teamMemberNo }) if (res.code 200) { uni.showToast({ title: 报名成功, icon: success }) setTimeout(() uni.navigateBack(), 800) } } } } /script报名列表的刷新时机也很重要。我建议在 onShow 里拉数据而不是 onLoad。因为学生从报名详情页返回的时候列表页的剩余名额和报名状态需要同步刷新如果只在 onLoad 里加载一次返回时看到的还是旧数据。4.3 与后端联调中的常见前端坑联调阶段最容易出问题的是跨域和 HTTPS 证书。本地开发时小程序开发者工具可以勾选信任域名但真机不行。我建议项目一开始就把接口统一封装成一个 request.jsbaseURL 从配置文件读取后边切换地址只改一处。另一个高频坑是“uniapp 中获取路由的参数”。我常用的传参方式有两种复杂对象用全局变量或本地存储简单 ID 用 URL query。不要图省事把整个项目对象拼在 url 上小程序页面栈和路径长度有限对象序列化后很容易超长而且刷新后参数还可能失效。还有一个实际遇到过的问题微信小程序的软键盘会遮挡查询框。在报名管理页里老师需要按学生姓名或学号搜索报名记录一弹软键盘查询框就被顶上去体验很差。解决办法是在 manifest 里对 App 配置键盘模式小程序端则是用 uni.pageScrollTo 在 focus 的时候把页面滚上去或者在 input 外层做一个吸底容器动态监听键盘高度调整位置。实测下来监听键盘高度并给底部留白是最稳妥的方案。4.4 自定义分享与全局方法覆盖问题热词里有一条“uniapp onShareAppMessage 被全局方法覆盖”这个问题我在做成绩排行分享功能时也遇到过。Uniapp 页面内如果定义了 onShareAppMessage项目全局的 share 逻辑就会被页面覆盖导致分享标题和图片不一样。我的方案是封装了一个可复用的 shareMixin在需要分享的页面里混入这个 mixin页面只要指定 shareTitle 和 sharePath 就行这样既不会全局覆盖每个页面又能定制化。如果你没做过有一个更简单的思路在 App.vue 的 globalData 里存一份 share 配置页面 onShareAppMessage 里直接读取 globalData也能避免重复代码。5. 常见问题排查与优化建议5.1 微信小程序审核与隐私合规小程序做完之后提审是一个绕不过去的坎。运动会报名系统涉及用户姓名、学号、性别这些个人信息如果你在小程序端收集了这些字段就必须在用户首次打开时弹隐私政策并且在小程序后台的“用户隐私保护指引”中如实声明收集的信息类型。很多开发者不知道这个结果第一次提审直接被拒。我在项目里做了一版隐私弹窗用户进入首页先让用户阅读并同意隐私政策同意之前不调任何获取用户信息的接口。这里有个官方的做法在 app.json 里声明usePrivacyCheck: true然后在需要的时候判断用户有没有同意授权没有就弹窗。还有一点如果系统里涉及微信支付务必提前确认小程序类目和支付渠道是否合规。高校运动会报名一般免费我这边没有接支付如果你们要收报名费或保险费用就要提前申请微信支付商户号还要保证类目与业务一致否则支付功能很容易被限制。5.2 上架安卓应用市场和打包相关有些学校希望把运动会报名系统同时做成安卓端 App一个 Uniapp 项目也能打包。热词里“uniapp怎么打包”问的人很多流程其实不复杂HBuilderX 里选择“发行-原生App-云打包”填好 Android 证书等云端打包完成下载 apk 即可。这里提醒一下云打包需要你自己生成 keystore 签名文件签名信息会直接影响后续应用升级一定要把密码和别名记好丢了基本救不回来。另外打包出来的 App 要上架安卓应用市场不同平台要求不一样有的要软著有的要隐私政策链接。校园内部使用的话一般直接通过官方应用商店的企业分发或二维码下载就好不需要逐个上架渠道。5.3 性能优化与后续扩展方向一套校园运动会报名系统正常情况下并发量不大初期不必上太复杂的基础设施。但如果全校几百人同时报名同一个爆款项目还是要防一下数据库连接被打满。我这边做了三个优化接口层面用 Redis 缓存项目列表的热点数据数据库连接池参数调整设置合理的初始连接数和最大连接数报名接口加限流同一用户禁止高频重复提交。后续扩展方向的话可以往这几个方向走一是消息通知比赛审核通过、赛前提醒都可以通过小程序订阅消息推给学生二是成绩排行榜前端可以加一个实时榜单增强观赛氛围三是数据导出老师端把报名名单、成绩单导出成 Excel省去手动汇总。一些真实使用体验我最后再说几个开发这套系统时比较实在的体会第一个是报名人数实时刷新真的不能靠学生手动下拉刷新。我一开始只做了下拉刷新学生反馈要看个名额还得自己刷新很麻烦后来我加了页面 onShow 回刷和定时轮询报名高峰期的时候体验才好起来。第二个是后端接口字段命名要跟前端保持一致别搞什么 user_id 配 userId 这种自行转换的事。我前期随手写了几个不一致的字段结果前端用拦截器统一处理时总有几个接口要特殊适配后来回头统一规范了一遍整个世界清净了。第三个是工具链千万不要乱升级。开发中途我把 SpringBoot 升到 3.0 试了试结果一堆依赖要跟着升折腾了整整一天。校园系统这种东西稳定永远大于追赶版本。你换框架不如先把业务逻辑写扎实。系统上线以后体育部老师反馈最明显的是“终于不用一遍遍对名单了”。这就是我个人做这类系统比较大的成就感。大学运动会系统真的不难你只要把报名流程和审核流程的数据模型理清楚前后端技术上选自己熟悉的栈一步步把它实现出来就好。如果你们学校也正好要搞运动会报名希望这篇东西能帮你省下一部分试错的时间。
返回列表