基于Spring Boot+Vue的村超民运会赛务报名管理系统设计与实现 📅 发布时间:2026/9/16 8:15:21 👁 浏览次数: 2024年我在贵州那边做县域体育赛事信息化接到了一个村超和民运会赛务报名管理系统的活儿。这类系统听起来不大真做起来才发现从运动员资格审核到赛程编排从报名截止控制到成绩录入汇总每一环都有人会在线上等着催你改需求。后来整个项目基于 Java Vue Spring Boot 这套组合落地前端用 Vue 做单页应用后端用 Spring Boot 提供接口数据库走 MySQL整个开发周期压缩到了三周半上线后扛住了几百支队伍同时报名的并发压力。今天就把这个系统的设计与实现思路拆开来讲包括需求拆解、数据模型、核心代码、部署踩坑给正在做同类信息系统的人一个可以直接参考的样板。我自己做这类信息服务系统最深的体会是村超、民运会这种基层赛事它不是没有报名流程而是流程散落在微信接龙、纸质表格、Excel 和电话里边。系统最核心的价值不是炫技而是把“谁在什么时候能报什么项目、报完怎么分组、比赛时成绩谁说了算”这串链条捋清楚。所以这篇文章会围绕这几块展开先说业务建模的思路再讲数据库和接口设计然后落到前后端具体实现细节最后把上线过程中最容易翻车的几个问题列出来。不管你是学生做毕业设计还是刚工作的开发人员接了类似项目都可以照着自己改一版。1. 项目整体设计与思路拆解1.1 村超/民运会的赛务场景到底有哪些痛点村超这几年火遍全网本质上是乡村足球超级联赛但“村超”这两个字已经变成了一种基层群众体育赛事的代名词。民运会则是少数民族传统体育运动会比赛项目就更杂了有竞赛类的有表演类的还有民族式摔跤、押加这类特色项目。这两种赛事放到一起做系统最大的难题是“赛事规则不统一”。同一个系统里可能这边是足球队11人制在报名那边是押加个人赛在报名还有一支表演队只报一个节目、三个人上场根本没法用一套固定的字段模板去套。我在做需求调研时组委会提得最多的三个痛点是第一报名信息反复核对太累。参赛队伍领队交上来的报名表有的用 Excel有的用手机拍照有的直接微信发一段文字。工作人员要把这些信息重新录入电脑一不小心就会把身份证号输错一位等做秩序册的时候发现运动员名字和证件对不上又是连环电话。第二项目冲突和资格审核靠人眼。个人项目里的运动员经常同时报多个项目比如一个押加运动员可能同时报 68 公斤级和 76 公斤级但赛事规程规定每人只能报一个级别这种逻辑靠人工检查很容易漏必须靠系统规则去卡。第三赛程和成绩的数据孤岛。报名系统往往和检录、计分、成绩公布是脱节的报名数据导出来给裁判组以后裁判组统计完成绩又要手动录回系统。我在设计时干脆把报名和成绩放在同一个系统里比赛当天可以直接在终端上录入成绩实时生成积分榜省掉中间导表格的环节。1.2 技术选型为什么是 Spring Boot Vue 这套组合市面上做信息管理系统有成型的低代码平台也可以直接用 PHP 或者 Python 快速搭。但这里有几个硬性条件学校、县政府信息中心那边希望系统能部署在他们的内网服务器上对数据库和中间件有明确的国产化或者说自主可控要求后期可能要接入人脸识别检录、大屏数据展示这类硬件设备系统必须提供标准接口开发周期又紧前后端不能互相等。Spring Boot 在这种项目里优势很明显。它基于 Spring 生态自动装配机制把大量繁琐的 Bean 配置省掉了我只需要在 pom 里引入对应 starter再写几个配置项就能把 Web、持久层、缓存、安全这些能力组合起来。更关键的是Spring Boot 的社区资料极度丰富遇到问题搜索引擎一找一大片这对小团队来讲就是保命符。Vue 作为前端框架单页应用的开发体验比传统 JSP 厚模板舒服得多组件化拆分开发现场报名信息录入界面非常高效而且 Vue 的学习曲线平缓招人也好招。数据库我选了 MySQL 8.0ORM 用 MyBatis-Plus。选 MyBatis-Plus 而不是 JPA是因为这类系统的 CRUD 操作太多了而且会经常写一些多表关联加聚合统计的 SQLMyBatis-Plus 既保留了 MyBatis 灵活写 SQL 的能力又提供了 BaseMapper 这种开箱即用的单表操作封装。缓存的引入是上线前临时决定加的报名高峰 Close 之前那一下报名接口的 QPS 会突然暴涨我用 Redis 缓存赛事基础配置和运动员资格校验结果实测效果非常明显。2. 核心细节解析与实操要点2.1 系统角色与功能边界划分赛务报名系统跟普通的企业 OA 不一样它的角色天然就是多端的。我在设计权限模型时划分了五个端系统管理员管账号分配、赛事创建、系统配置相当于技术后台的超级管理员。组委会/赛务组负责发布赛事规程、审核报名信息、编排赛程、录入成绩、发布公告。领队/教练代表队伍操作可以创建队伍、添加运动员、选择参赛项目、提交报名。运动员查看自己的报名状态、赛程安排、成绩结果。裁判/检录员移动端或者电脑端录入成绩打印检录表。这里有个容易忽略的点领队和运动员并不是天然分开注册的。我设计的是“先注册账号后申请领队身份”的流程。运动员注册之后可以绑定到某个队伍这时候他自动成为该队伍的队员而领队身份需要通过队伍创建或管理员授权才能获得。这样能避免所有人都能随便建队伍造成的数据污染。前端路由和后端接口权限是两个层面的事情。前端通过路由守卫控制页面能不能进入后端通过拦截器注解校验接口权限。我用了自定义注解 RequiresRoleAOP 拦截器里判断当前登录人的角色编码是否在允许列表里如果不在就直接返回 403。这样就算有人绕过前端直接调接口也拿不到其他角色的数据。2.2 数据库表结构与字段设计思路这类系统的表设计其实比很多企业系统有意思因为它有“赛事—项目—队伍—运动员”这套多对多的嵌套关系。我最终落地的核心表如下赛事表event主要存赛事名称、举办时间、状态、报名开始/结束时间。这里的状态字段我用了枚举值表示1 未开始2 报名中3 报名截止4 进行中5 已结束。为什么不用字符串直接存因为后续状态流转要做条件查询比如WHERE event.status 2如果存的是中文还得先处理编码字符串大小写不统一也是坑直接用 int 加枚举类在 Java 层面对应简洁高效。项目表competition_item要解决“项目类型不一致”的问题。我是这样设计的项目归属赛事项目有名称、项目类别竞赛类/表演类/民族式摔跤/押加、项目类型个人/团体、限制人数男/女、参赛级别、报名费用。团体项目会在关联表里说明“每队最少人数/最多人数”个人项目则用级别字段区分。队伍表team记录参赛单位信息比如代表队名称、单位类型乡镇/学校/社会团体、领队信息。运动员表athlete是核心姓名、身份证号、性别、民族、出生日期、照片、所属队伍。这里身份证号我做了唯一索引同一个身份证号不允许注册到两个不同队伍。报名表registration是业务的核心表记录队伍针对某个赛事报了哪个项目包含审核状态0 待提交1 待审核2 已通过3 已驳回。如果报名被驳回驳回原因字段必须保留不然领队不知道哪里错了。把报名拆成“报名单主表 报名明细表”是这类系统比较稳妥的做法。队伍先创建一张报名单然后在明细里添加运动员对于团体项目还需要指定队长和上场位置。这样一张报名单就是一个完整的申报包组委会审核时只需要对单审批不需要在多个界面来回跳。2.3 报名状态机的设计与流程控制报名状态的推进是整个系统最容易出 bug 的地方。我见了太多系统把状态存在一个字段里然后用 if-else 到处判断最后状态改来改去自己都搞晕了。这里我直接用状态机模式给报名单定义清晰的状态流转路径草稿 → 已提交 → 审核中提交后进入 → 已通过 / 已驳回。驳回后领队可以编辑重新提交重新提交后状态回到审核中。我在接口层做了状态流转的校验方法用一组 Map 定义允许的流转路径比如草稿状态只允许执行“提交”操作已驳回状态只允许执行“编辑”或“重新提交”操作已通过状态不允许任何修改。这种写法的好处是后面加新需求比如“报名截止后组委会可以强制退回某条报名”只要在这张流转表里加一条路径改起来非常快。报名人数限制的控制也有讲究。比如一个团体项目限报 8 人实际提交时发现人数超了此时应该在前端就拦截住。前端的校验依赖比赛项目配置里的人数上限而后端提交接口也必须再查一次数据库实时校验因为存在多个领队同时操作的并发场景前端校验只是体验层面的保障后端校验才是正确性的兜底。具体做法是在提交报名的事务里加上SELECT ... FOR UPDATE行锁锁住报名单记录然后再判断明细表当前人数防止超限。3. 实操过程与核心环节实现3.1 Spring Boot 工程结构与关键依赖我习惯用 Spring Initializr 创建工程Java 版本用 17Spring Boot 版本一开始用的 3.2.x。这里要提醒一件事Spring Boot 3.x 对 Java 版本有硬性要求最低是 Java 17如果你本地环境还在用 Java 8那就老老实实选 Spring Boot 2.7.x。不同版本对应的组件坐标也不一样比如 Spring Boot 3.x 里很多 spring 官方库的 groupId 从org.springframework.boot调整或升级了版本网上搜资料时看到 2.x 的解决方案可能直接套不上这是“springboot版本太高”这个热搜词最常见的来源。核心依赖我会贴一下 pom.xml 片段dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version /dependency /dependenciesRedis 在这里不是必须的但如果你打算做验证码、防重复提交、缓存热数据提前引入准没错。Hutool 是我个人很喜欢的一个工具库身份证校验、日期处理、Excel 导出这些功能都有封装能省不少重复代码。application.yml 里比较重要的配置项是数据源、Redis 连接、MyBatis-Plus 的驼峰映射和逻辑删除配置。这里我直接贴一份实际用的配置server: port: 8080 servlet: context-path: /api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/sport_register?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 data: redis: host: localhost port: 6379 database: 0 timeout: 3000ms mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml configuration: map-underscore-to-camel-case: true 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关于逻辑删除这类赛事系统不建议对运动员报名记录做物理删除。比赛数据是要归档留痕的出了问题要看历史记录物理删除会留下无法弥补的审计盲区。用逻辑删除字段标注所有的查询都默认过滤掉 deleted1 的数据既保证界面看不到废数据又保住了历史。3.2 Vue 工程搭建与前端路由设计前端用 Vite Vue 3 的组合包管理工具用 npm。Vue 环境配置这件事在热词里反复出现其实要点就几个Node.js 版本必须满足 Vite 的要求我用的 Node 18 LTSnpm install 时如果遇到权限问题就试试npm install --registryhttps://registry.npmmirror.com装完以后跑npm run dev能起服务就算基础环境通了。项目结构我这么拆src ├── api # 接口请求封装 │ ├── auth.js │ ├── event.js │ ├── registration.js │ └── result.js ├── assets ├── components # 公共组件 │ ├── StatusTag.vue │ └── PaginationTable.vue ├── router │ └── index.js ├── store │ └── user.js ├── views │ ├── login.vue │ ├── event │ ├── registration │ ├── schedule │ └── result └── utils └── request.js路由设计这里有个关键点村超/民运会这种多角色系统前端路由不应该全部静态写死。我在 router/index.js 里把每一个页面都登记了 meta 信息包含 requireAuth、roles 两个字段然后在全局前置守卫里检查。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requireAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) return } const userRole store.state.user.role if (to.meta.roles !to.meta.roles.includes(userRole)) { next({ path: /403 }) return } next() })Vue 路由传参在报名详情页跳转时最常用。我跳到一个报名单详情页面时用的是/registration/detail?id123这种方式然后this.$route.query.id拿参数。为什么不用 params因为 params 传参在刷新页面后会丢失用 query 才是可刷新、可收藏的链接。这在订单详情、报名详情这类需要分享给其他人查看的页面里非常重要。3.3 登录鉴权与验证码实现登录这块我没有引入 Spring Security因为这个项目需要管理的用户角色简单引入 Security 只会增加配置复杂度。我用拦截器 JWT 的方式实现。用户输入账号密码后后端校验通过返回一个 token前端把 token 存在 localStorage每次请求在 axios 拦截器里把 token 塞进请求头。后端拦截器从请求头取 token解析成功才算认证通过。验证码我用的 Redis 存。给一个 key比如captcha:userId设置 5 分钟过期用户提交登录时把验证码一起提交后端比对 Redis 里的值和用户输入值相等才算通过。为什么不把验证码存 Session因为这个后端接口可能会被手机端复用Session 在跨端场景下管理维护麻烦Redis 天然支持过期时间而且多个后端实例部署时共享一份缓存没有 Session 同步问题。JWT 生成和解析我用的是 jjwt 库代码不长public String createToken(User user) { return Jwts.builder() .setSubject(user.getId().toString()) .claim(username, user.getUsername()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 12)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }这里 token 有效期设了 12 小时因为比赛期间领队们基本上整天都在用系统太短会频繁被踢下线太长又有安全风险。如果要做更完善的控制可以在 Redis 里存一份 token 的 jti实现主动踢人下线但当前项目没到那个复杂度后续可以扩展。登录接口的另一个安全细节是防暴力破解。我在 Redis 里记录同一个账号连续输错密码的次数超过 5 次之后锁定 15 分钟。底层原理其实就是最简单的一个计数器 过期时间代码量很少但能挡住很多低级扫描。3.4 队伍报名核心接口的代码实现报名提交接口是整个系统业务逻辑最重的地方我把它单独讲。前端领队选择赛事项目后创建报名单再往报名单里添加运动员。前端页面长这样左边是项目树右边是当前报名单的运动员列表。保存草稿时只把报名单主记录和明细记录插入数据库提交审核时后端先做完整性校验再做资格校验最后改状态为待审核。核心方法大致是这样Transactional(rollbackFor Exception.class) public Long submitRegistration(RegistrationSubmitDTO dto) { Registration reg registrationMapper.selectByIdForUpdate(dto.getRegistrationId()); if (reg null || !reg.getStatus().equals(RegistrationStatus.DRAFT.getCode())) { throw new BizException(报名单不存在或状态不允许提交); } ListRegistrationItem items registrationItemMapper.selectList( new LambdaQueryWrapperRegistrationItem() .eq(RegistrationItem::getRegistrationId, reg.getId())); if (CollectionUtils.isEmpty(items)) { throw new BizException(报名明细不能为空); } // 校验项目人数限制 CompetitionItem item itemMapper.selectById(reg.getItemId()); if (items.size() item.getMaxAthletes()) { throw new BizException(报名人数超过项目上限); } // 校验是否重复报名同一赛事、同一队伍、同一运动员、同一项目 long repeatCount registrationItemMapper.selectCount( new LambdaQueryWrapperRegistrationItem() .eq(RegistrationItem::getAthleteId, reg.getAthleteId()) .eq(RegistrationItem::getEventId, reg.getEventId()) .eq(RegistrationItem::getItemId, reg.getItemId()) .ne(RegistrationItem::getRegistrationId, reg.getId())); if (repeatCount 0) { throw new BizException(该运动员在此项目中已存在报名记录); } reg.setStatus(RegistrationStatus.PENDING.getCode()); registrationMapper.updateById(reg); return reg.getId(); }有些比赛的报名要交纸质材料比如体检证明、免责声明。我在系统里提供了附件上传功能用本地磁盘存储上传成功后在数据库存文件路径。附录的体检表往往是组委会审核时重点看的内容所以上传附件字段设计成了非必填但如果项目类型是“体能竞赛类”后端会自动校验附件列表不能为空。3.5 赛程编排与成绩录入的联动实现赛程编排这里一般的做法是导入秩序册 Excel然后系统根据项目、组别、日期生成比赛场次。我用的是“先有参赛名单再按规则生成对阵”的思路。足球项目需要先分组抽签抽签做在系统里点击“自动分组”后系统把报名队伍按地域和种子队伍分成若干小组。分组算法其实不复杂就是先排种子队再轮转填组。摔跤、押加这类个人项目则生成淘汰表首轮轮空规则系统能自动算。成绩录入端我单独做了一个适配手机和平板布局的页面裁判只需要先选比赛项目再选场次然后录入运动员得分或者胜负关系。录入完成后系统根据规则自动计算每个单位的总分并实时刷新到成绩榜页面。这个功能是赛务系统里最容易出性能问题的地方因为大屏成绩榜页面每隔几秒就要刷新一次。我的做法是成绩榜接口加了 Redis 缓存成绩录入后主动删除对应缓存而不是让前端盲目轮询数据库。与之配套的还有检录表打印。每个比赛项目开赛前检录员要打印带有二维码的检录表运动员报到时扫码确认身份并签到。二维码内容存的是比赛场次 ID后端提供查询接口返回该场次的运动员名单。这个环节能把“人到了没有”变成系统实时数据对组委会统筹比赛进度帮助很大是我这个项目里让用户印象最深的功能权之一。4. 常见问题与排查技巧实录4.1 Spring Boot 版本过高导致的匹配问题做这个项目时我踩过一个很典型的坑。项目开始时选的 Spring Boot 3.2.2Java 17开发环境一切正常。等部署到服务器上运维那台机器装的是 JDK 8结果 java -jar 启动直接报UnsupportedClassVersionError。后来一查Spring Boot 3.x 编译产物是 Java 17 字节码JDK 8 根本跑不了。解决办法有两个要么把服务器 JDK 升到 17要么把项目降级到 Spring Boot 2.7 系列 Java 8。最终我给运维重新装了 JDK 17问题解决。另一个和版本相关的问题是“springboot版本太高”带来的配置项变更。Spring Boot 2.7 里spring.redis.*的配置前缀到 Spring Boot 3.x 变成了spring.data.redis.*javax.annotation包里的注解在 Spring Boot 3.x 中变成了jakarta.annotation。如果你从旧项目复制一份配置过来最容易忘记改这两个地方。排查方法很简单看控制台启动日志配置项无法识别时会打印 warning别忽略它。4.2 Vue 打包后布局异常与刷新 404开发环境 npm run dev 一切正常npm run build之后打开 dist/index.html 页面空白或者刷新某个路由直接 404这是 Vue 项目最常见的问题。原因我知道的人都懂但第一次遇到还是会慌。页面空白通常是因为资源路径写成了绝对路径。默认 Vite 构建的 base 是/部署到服务器根目录没问题但如果放到子目录比如http://ip:8080/web/资源路径就会找错。解决方法是调整 vite.config.js 里的 base:export default defineConfig({ base: ./, plugins: [vue()], })刷新 404 是因为 Vue Router 默认用的是 history 模式路径是真实的路由路径而服务器上并没有对应的物理文件刷新时服务器去查这个路径发现不存在就返回 404。解决方案有两种一是把路由改成 hash 模式URL 里会多个#不美观但没有配置成本二是在 Nginx 里做 try_files 回退location / { try_files $uri $uri/ /index.html; }我项目最终用的是 Nginx 回退方案因为 URL 干净而且通过 Nginx 反向代理到后端接口也很方便。另外还有一类 Vite 打包后特性丢失的问题比如按键回车登录失效可能是默认公共组件打包时被 tree-shaking 掉了这种情况要看具体代码不能一概而论。4.3 跨域问题与前端请求封装前后端分离部署必然遇到跨域。开发阶段我用的 Vite 代理server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } }生产环境则通过 Nginx 统一反向代理前端访问/api时由 Nginx 转发到后端服务这样浏览器看到的还是同源请求。跨域问题能不能在服务端直接加CrossOrigin解决能但生产环境不推荐因为你把接口暴露给任何域名的前端都能调用安全风险大。统一走 Nginx 反代是标准的做法。前端请求封装我在 utils/request.js 里基于 axios 做了统一拦截器请求时带上 token响应时统一处理业务码。这里有个细节如果后端返回 401前端应该清除本地 token 并跳转登录页同时要防止多个接口同时返回 401 导致重复跳转。我的做法是加一个标志变量保证同一时间只跳转一次。4.4 并发报名时人数超限的兜底方案线上运营时遇到过一次真实的并发问题。某个热门项目报名开放时间是早上 9 点结果 9 点整几十支队伍同时点提交数据库的报名明细表瞬间出现同一运动员多条记录后台上看到一个人报了三次同一个项目。原因就是前端校验通过后后端校验重复和人数限制时没有加锁两条请求同时查到人数未满同时插入成功。我是这么修的在项目表上加一个 current_count 字段提交报名时用乐观锁或者UPDATE ... SET current_count current_count 1 WHERE current_count max_count这种原子操作去更新影响行数为 0 就说明人数已满直接返回报错。这个方案比纯靠 SELECT 判断再 INSERT 稳得多实际测试 500 并发下没有超限记录。还有一个跟并发相关的点就是热词里说的“java线程等待都完成”。比如生成一个大型赛事的秩序册要异步拉取几十个项目的数据再合成 PDF我用了 CompletableFuture 并行查询然后用allOf().join()等待全部完成。这里要注意join 等待的任务如果某个子任务抛出异常会导致整个流程卡住或异常所以在子任务里要 catch 掉所有异常并记录日志绝不能让它把主流程带崩。5. 部署上线与维护扩展5.1 打包部署与命令记录后端打包依然是标准的mvn clean package -Dmaven.test.skiptrue打完的 jar 放在服务器上用 systemd 守护运行。我贴一个自己常用的 systemd 服务配置避免进程被意外杀掉后无人重启[Unit] DescriptionSport Register Server Afternetwork.target [Service] Typesimple Userroot WorkingDirectory/opt/sport-register ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar sport-register.jar SuccessExitStatus143 Restartalways RestartSec10 [Install] WantedBymulti-user.target前端打包后 dist 目录传到服务器Nginx 的配置如下server { listen 80; server_name your-domain.com; root /opt/sport-register-web/dist; index index.html; location / { 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }注意 proxy_pass 最后那一段有没有斜杠语义不一样。如果后端接口路径是/api/auth/login而 proxy_pass 写的是http://127.0.0.1:8080那么请求会被转发到http://127.0.0.1:8080/api/auth/login后端 context-path 是/api时就正好匹配。如果写反了后面会出现 404 或者接口路径多一层少一层的恶心问题。5.2 数据备份与日志处理赛事数据的重要性不用多说。MySQL 我每天凌晨 3 点跑一次全量备份备份文件保留最近 14 天。shell 备份脚本的核心就一行命令mysqldump -uroot -ppassword sport_register | gzip /backup/mysql/sport_register_$(date %Y%m%d_%H%M%S).sql.gz日志方面Spring Boot 默认输出到控制台用 systemd 管理后 journald 会收集日志用journalctl -u sport-register -f可以实时看。但如果日志量太大建议在 application.yml 里配置 logback 滚动日志文件按天切分保留 15 天。赛务系统比赛当天日志量特别大不做轮转会把磁盘撑满。5.3 后续功能扩展的三个方向系统上线后能扩展的地方其实非常多。第一是移动端适配现在前端是响应式的但很多领队习惯用手机操作加一个小程序或者 H5 端体验会好很多。第二是赛事直播和视频回放如果后续要做直播前端要处理 m3u8 流的播放通常会用到 video.js 加 hls.js 的方案这不是必须但现在球迷呼声很高。第三是数据大屏对接 LED 大屏实时展示各项目奖牌榜、积分榜后台接口已经具备数据基础只需要再写一个面向大屏适配的只读接口前端用 ECharts 把数据动态渲染出来即可。另外如果领导们要求生成各种统计报告比如“某乡镇报名了多少人、某少数民族项目参与率如何”就需要把报名表数据再做一层宽表聚合。我在数据库里预留了统计视图比如v_event_athlete_stats这里直接用 SQL 的 GROUP BY 生成统计结果然后导出 Excel。热词里提到 java 相关基础八股其实这类需求考的就是 SQL 和集合处理的基本功真用起来会发现任何框架都替代不了这些基础能力。6. 个人实操体会与建议做完这个村超民运会赛务报名管理系统我最大的体会是技术选型反过来受用户的使用习惯影响很大。这些领队、裁判不少是乡镇干部或者学校体育老师他们不会像互联网产品用户一样自己去摸索系统需要系统把流程收窄到“下一步”、“返回”这样的一步步引导。所以在页面设计上我让报名向导分四步走选项目添运动员传附件确认提交。每一步都有明确的错误提示尽量不让用户思考“我该点什么”。另一个让我印象很深的点是尽量在系统里减少人工录入的字段能下拉选的不让手填能根据身份证自动带出的不让人再输一遍。现在身份证号一输出生日期、性别、年龄都能解析出来Hutool 的 IdcardUtil 一把梭用户的录入成本降低了错误率也降了。最后再给打算做类似系统的同学一条建议这类信息管理系统的开发难度不在编码在需求拆解。动手写代码之前多花两天时间找几个真实用户聊一遍流程把边界场景都列出来画清楚状态图后面写出来的代码会少改很多轮。我这次项目里光是“团体项目能不能替换队员”“报名截止后能不能补报”这两个问题需求阶段反复确认了三遍事实证明每一遍都是值得的。系统上线后组委会最满意的一句话是“原来要干两周的报名统计现在一天就出汇总表了”。对一个开发者来说这一句话比任何技术上的赞美都有成就感。