尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring Boot实战:高并发运动会报名系统后端设计与优化

Spring Boot实战:高并发运动会报名系统后端设计与优化 简介在Web应用开发中数据库事务与并发控制是保障数据一致性的核心技术。其原理在于通过锁机制或乐观锁版本控制确保在高并发场景下多个操作能有序执行防止数据错乱。这项技术的价值在于支撑高可用、高并发的在线业务系统广泛应用于电商秒杀、票务预订、在线报名等场景。本文以运动会报名系统为例深入探讨了在Spring Boot框架下如何结合MySQL与MyBatis-Plus通过设计唯一索引和实现乐观锁方案有效解决报名场景下的“超卖”问题并分享了数据库设计、复杂查询构建等工程实践。1. 项目概述一个运动会报名系统的后端蓝图最近在整理过往项目时翻到了一个挺有意思的“运动会报名管理系统”的后端设计源码。这个项目虽然听起来像是校园或企业内部的一个常规应用但麻雀虽小五脏俱全它完整地串联了从用户报名、项目编排到数据统计的整个业务流程。当时选用的是 Spring Boot 作为后端框架搭配 Vue 做前端算是一个非常经典且实用的技术栈组合。今天我就把这个后端部分的设计思路、核心实现以及踩过的那些坑掰开揉碎了和大家聊聊。无论你是刚接触 Spring Boot 想找个实战项目练手还是正在规划类似的管理系统相信这篇分享都能给你一些直接的参考。这个系统的核心目标很明确高效、准确、无纸化地处理运动会报名事务。想象一下传统运动会报名要么是纸质表格满天飞要么是 Excel 文件传来传去数据汇总和核对简直是噩梦。我们这个系统要解决的就是把这些流程搬到线上让组织者能轻松管理项目和参赛者让参与者能便捷地查看信息、完成报名和查询结果。后端作为整个系统的“大脑”和“数据枢纽”其设计的健壮性、扩展性和安全性直接决定了用户体验的好坏。2. 整体架构设计与技术选型考量2.1 为什么是 Spring Boot MySQL MyBatis-Plus当初技术选型时市面上可选的后端框架很多。最终锁定 Spring Boot主要是基于以下几点实战考量快速启动与约定大于配置Spring Boot 的自动配置和起步依赖Starter特性让我们能快速搭建起一个可运行的项目骨架。对于“运动会报名”这类业务逻辑明确但开发周期可能不长的项目来说这能节省大量在环境配置、依赖管理上的时间。比如通过spring-boot-starter-web直接引入 Web 开发所需的一切通过spring-boot-starter-data-redis轻松集成缓存无需手动处理一堆 XML 配置。微服务友好与生态成熟虽然我们这个单体应用暂时用不上微服务但 Spring Boot 本身就是 Spring Cloud 微服务体系的基石。采用它意味着如果未来业务膨胀需要将用户服务、报名服务、成绩服务拆分开迁移和改造的成本会低很多。此外Spring 庞大的生态圈意味着几乎所有你可能需要的功能如安全控制、定时任务、消息队列都有成熟、稳定的解决方案或社区支持。数据库与ORM层的选择数据模型上运动会报名涉及用户、项目、报名记录、成绩等实体关系相对清晰但关联查询频繁。MySQL 作为成熟的关系型数据库在事务一致性、复杂查询支持方面表现可靠社区资源丰富运维成本低。ORM 层没有选用 JPA 而是 MyBatis-Plus主要出于对 SQL 灵活性的要求。报名系统里会有不少多表关联、动态条件统计的查询例如统计各学院报名人数、查询某运动员的所有参赛项目MyBatis-Plus 在提供类似 JPA 的便捷 CRUD 接口IServiceQueryWrapper的同时保留了手写复杂 SQL 的能力这种“半自动化”的模式在复杂业务场景下更得心应手。它的代码生成器也能一键生成 Entity、Mapper、Service 基础代码进一步提升开发效率。2.2 后端核心模块划分根据业务逻辑我将后端划分为以下几个核心模块这不仅是代码的物理划分更是职责的清晰界定用户管理模块处理学生、教师、管理员等角色的注册、登录、信息维护、权限认证如基于角色的访问控制 RBAC。这是系统的入口和安全的基石。运动会项目管理模块负责运动会、比赛大项如田径、游泳、具体小项如男子100米、女子跳高的增删改查。需要定义项目的属性如最大报名人数、性别限制、年级限制、报名起止时间等。在线报名模块系统的核心业务流程。处理用户提交报名申请、校验报名资格是否重复报名、是否符合项目限制、更新项目已报名人数等。这里涉及高并发下的数据一致性问题防止超报。成绩管理模块在比赛结束后由授权人员录入、审核、发布成绩。支持按项目、按个人、按团体进行排名和成绩查询。数据统计与报表模块为组织方提供数据支持如各学院报名热度分析、项目参赛人数统计、成绩汇总报表等。系统支撑模块包括全局异常处理、统一响应封装、日志记录、接口文档Swagger/OpenAPI生成、配置管理等。这些是保障系统健壮性和可维护性的基础设施。注意模块划分的粒度需要权衡。过粗会导致单个模块臃肿过细则会增加模块间调用的复杂度。我的经验是初期可以按核心业务实体划分随着业务复杂再逐步重构。例如“报名”和“成绩”一开始可能在一个“业务核心”模块里当两者逻辑都变得复杂时再拆分开。3. 数据库设计与核心实体关系解析数据库设计是后端系统的地基设计得好后续开发事半功倍设计得不好则可能处处掣肘。3.1 核心表结构设计我设计了以下几张核心表它们之间的关系构成了整个系统的数据模型骨架用户表 (sys_user)存储所有系统用户信息。关键字段包括id主键、username学号/工号、password加密存储、real_name、college学院、grade年级、role角色学生、裁判、管理员等、status状态。运动会表 (sports_meet)定义一届运动会。字段如id,name如“第XX届春季运动会”start_time,end_time,status筹备中、进行中、已结束。比赛项目表 (sport_item)定义具体的比赛项目。这是设计的重点需要足够灵活以容纳各种约束。CREATE TABLE sport_item ( id bigint NOT NULL AUTO_INCREMENT, sports_meet_id bigint NOT NULL COMMENT 所属运动会ID, item_name varchar(100) NOT NULL COMMENT 项目名称, category varchar(50) COMMENT 类别如田赛、径赛、球类, gender_limit tinyint COMMENT 性别限制0-男1-女2-不限, grade_limit varchar(200) COMMENT 年级限制可存储JSON数组如[2021,2022], max_participants int DEFAULT 0 COMMENT 最大报名人数0表示不限, current_participants int DEFAULT 0 COMMENT 当前已报名人数, sign_up_start datetime COMMENT 报名开始时间, sign_up_end datetime COMMENT 报名结束时间, status tinyint DEFAULT 1 COMMENT 状态1-可报名0-不可报名, PRIMARY KEY (id), KEY idx_meet_id (sports_meet_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT比赛项目表;设计思考grade_limit字段使用 JSON 格式存储而不是另建一张关联表是因为年级限制通常是项目的一个简单属性列表查询时直接解析 JSON 即可避免了多表关联的复杂度。这是一种在灵活性和查询效率之间的权衡。对于更复杂的、需要关联查询的属性则应使用关联表。报名记录表 (registration)核心业务表记录每一次报名行为。CREATE TABLE registration ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, item_id bigint NOT NULL, sports_meet_id bigint NOT NULL, registration_time datetime DEFAULT CURRENT_TIMESTAMP, status tinyint DEFAULT 0 COMMENT 状态0-待审核1-报名成功2-已取消, cancel_reason varchar(255) COMMENT 取消原因, PRIMARY KEY (id), UNIQUE KEY uk_user_item (user_id, item_id, sports_meet_id) COMMENT 防止同一用户在同一运动会重复报名同一项目, KEY idx_item_id (item_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT报名记录表;设计思考唯一索引uk_user_item是保证业务逻辑正确性的关键从数据库层面杜绝了重复报名。status字段为后续扩展留出了空间例如可以加入“审核驳回”状态。成绩表 (score)记录最终比赛成绩。与报名记录关联。CREATE TABLE score ( id bigint NOT NULL AUTO_INCREMENT, registration_id bigint NOT NULL COMMENT 关联的报名记录ID, result varchar(50) COMMENT 成绩如“11.23秒”、“1.75米”, ranking int COMMENT 排名, score decimal(5,2) COMMENT 积分如用于团体总分计算, record_status tinyint DEFAULT 0 COMMENT 记录状态0-待录入1-已录入2-已发布, remark varchar(500), PRIMARY KEY (id), UNIQUE KEY uk_registration (registration_id), KEY idx_ranking (ranking) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT成绩表;3.2 实体关系与索引优化关系用户 N : 1 运动会一个用户可参加多届运动会用户 N : N 项目通过报名记录表关联项目 1 : N 报名记录报名记录 1 : 1 成绩。索引策略主键索引每张表的id字段InnoDB 引擎下即聚簇索引。唯一索引如registration表的uk_user_item防止数据重复。普通索引在所有常用于查询条件的字段上建立如sport_item表的sports_meet_idregistration表的user_id,item_id。这能极大提升根据运动会查项目、根据用户查报名记录等常见操作的效率。联合索引根据实际查询场景考虑。例如如果后台管理页面经常需要按“运动会项目状态”来筛选项目那么建立(sports_meet_id, status)的联合索引会比两个单列索引更高效。实操心得数据库字段注释一定要写清楚这不仅是给自己看的更是给后续维护的同事看的。使用utf8mb4字符集以支持完整的 Unicode包括 Emoji。对于像“年级限制”这种可能变化且结构简单的数据使用 JSON 类型存储比用逗号分隔的字符串更规范比建关联表更轻量。但切记JSON 字段内的数据无法直接建立索引如果需要对其中某个键值进行高效查询则此方案不适用。4. 核心业务逻辑实现与难点攻关有了清晰的数据模型接下来就是实现业务逻辑。这里我挑几个有代表性的核心功能点讲讲实现思路和遇到的坑。4.1 报名流程的并发控制与事务管理报名功能尤其是热门项目开启报名的瞬间很可能出现并发请求。核心问题就两个防止超报报名人数超过项目上限和保证数据一致性。最初的天真方案问题版// 伪代码 - 存在并发问题的版本 public boolean signUp(Long userId, Long itemId) { SportItem item sportItemService.getById(itemId); // 1. 检查是否已报满 if (item.getCurrentParticipants() item.getMaxParticipants()) { throw new BusinessException(该项目已报满); } // 2. 检查用户是否已报名唯一索引会兜底但提前检查友好 if (registrationService.isUserRegistered(userId, itemId)) { throw new BusinessException(您已报名该项目); } // 3. 创建报名记录 Registration reg new Registration(userId, itemId); registrationService.save(reg); // 4. 更新项目已报名人数 item.setCurrentParticipants(item.getCurrentParticipants() 1); sportItemService.updateById(item); return true; }问题在高并发下多个线程可能同时执行到第1步都判断为未报满然后都执行第3、4步导致current_participants最终超过max_participants。这就是典型的“超卖”问题。解决方案一数据库悲观锁SELECT ... FOR UPDATE在查询项目信息时加锁确保在事务提交前其他线程无法修改这条记录。Transactional(rollbackFor Exception.class) public boolean signUpWithPessimisticLock(Long userId, Long itemId) { // 使用MyBatis-Plus的wrapper进行加锁查询 SportItem item sportItemMapper.selectOne(new QueryWrapperSportItem() .eq(id, itemId) .last(FOR UPDATE)); // 关键加排他锁 // ... 后续检查与更新逻辑不变 // 由于在同一个事务中且该记录被锁定其他并发请求会在此阻塞直到当前事务提交。 }优缺点简单直接能保证强一致性。但缺点是会阻塞其他请求降低并发吞吐量且容易引发死锁需要谨慎控制事务范围和锁的粒度。解决方案二基于版本号的乐观锁在sport_item表中增加一个version字段整数类型默认0。Transactional(rollbackFor Exception.class) public boolean signUpWithOptimisticLock(Long userId, Long itemId) { SportItem item sportItemService.getById(itemId); // 检查逻辑... // 更新时带上版本号条件 boolean updated sportItemService.update(new UpdateWrapperSportItem() .setSql(current_participants current_participants 1) .eq(id, itemId) .eq(current_participants, item.getCurrentParticipants()) // 乐观锁条件当前人数未变 .lt(current_participants, item.getMaxParticipants())); // 并且仍未报满 if (!updated) { // 更新失败说明数据已被其他线程修改人数已变或已报满 throw new BusinessException(报名失败可能已报满或网络繁忙请重试); } // 创建报名记录... return true; }优缺点并发性能好无锁竞争。但失败率可能较高用户需要重试逻辑稍复杂。这是更推荐用于此类场景的方案。最终采用的方案我结合了两种思路。在项目层面使用乐观锁更新人数同时依靠数据库的唯一索引uk_user_item作为最终防线确保即使极端情况下人数更新逻辑有瑕疵也不会产生重复的报名记录。此外在业务入口处可以使用 Redis 分布式锁或令牌桶等限流手段平滑突发流量保护数据库。4.2 复杂查询与动态SQL构建后台管理页面经常需要根据多种条件组合查询报名记录或项目列表。例如“查询第10届运动会中计算机学院2022级男生在‘田径’类别下的所有报名情况”。这种查询条件动态变化使用 MyBatis-Plus 的QueryWrapper可以优雅地构建。public PageRegistrationVO queryRegistrationPage(PageRegistration page, RegistrationQueryDTO queryDTO) { QueryWrapperRegistration wrapper new QueryWrapper(); // 基础关联查询关联用户和项目表 wrapper.select(r.*, u.real_name, u.college, u.grade, i.item_name) .from(registration r) .leftJoin(sys_user u ON r.user_id u.id) .leftJoin(sport_item i ON r.item_id i.id); // 动态条件构造 if (queryDTO.getSportsMeetId() ! null) { wrapper.eq(r.sports_meet_id, queryDTO.getSportsMeetId()); } if (StringUtils.isNotBlank(queryDTO.getCollege())) { wrapper.eq(u.college, queryDTO.getCollege()); } if (StringUtils.isNotBlank(queryDTO.getItemCategory())) { wrapper.eq(i.category, queryDTO.getItemCategory()); } if (queryDTO.getGrade() ! null) { wrapper.eq(u.grade, queryDTO.getGrade()); } // 时间范围查询 if (queryDTO.getSignUpStartTime() ! null) { wrapper.ge(r.registration_time, queryDTO.getSignUpStartTime()); } if (queryDTO.getSignUpEndTime() ! null) { wrapper.le(r.registration_time, queryDTO.getSignUpEndTime()); } // 排序 wrapper.orderByDesc(r.registration_time); // 执行分页查询 PageRegistration registrationPage registrationMapper.selectPage(page, wrapper); // 将PageRegistration 转换为 PageRegistrationVO (视图对象) return convertToVOPage(registrationPage); }提示QueryWrapper虽然方便但在构造非常复杂的多表关联和嵌套查询时可读性和灵活性可能下降。对于极度复杂的查询我个人的习惯是直接在 XML 映射文件中编写清晰的 SQL 语句并在对应的方法上使用Select注解或调用 Mapper 的定制方法。MyBatis-Plus 的 Wrapper 和原生 SQL 能力可以混合使用择其善者而从之。4.3 权限控制与接口安全系统涉及不同角色学生报名、查成绩、裁判录入成绩、管理员管理项目、用户、查看所有数据。权限控制采用经典的RBAC基于角色的访问控制模型结合 Spring Security 或 Sa-Token 等框架实现。实现要点定义权限标识符为每个需要权限控制的接口或资源定义一个唯一的权限码如registration:add报名、score:input录入成绩、sport-item:delete删除项目。角色与权限关联在数据库中建立sys_role、sys_menu或sys_permission、sys_role_menu关联表。管理员角色拥有所有权限学生角色拥有registration:add、score:view等。接口注解控制使用PreAuthorize(“hasAuthority(‘registration:add’)”)这样的注解在 Controller 方法上声明所需权限。数据级权限有时不仅需要控制能访问哪个接口还要控制能看到哪些数据。例如学生只能看自己的报名记录。这通常在 Service 层实现在查询时自动添加“用户ID 当前用户ID”的条件。可以使用 MyBatis-Plus 的数据权限插件或自定义 AOP 切面来实现避免在每个查询方法里硬编码。安全加固密码存储使用 BCryptPasswordEncoder 对密码进行单向哈希加密存储绝对不要用明文或简单的 MD5。会话管理采用无状态的 JWT (JSON Web Token) 或有状态的 Session 管理。对于此类管理系统Session 更简单易控如强制下线。如果使用 JWT务必设置合理的过期时间并考虑 token 刷新和黑名单机制。输入校验使用 JSR-303 Bean Validation 注解如NotBlank,Email,Size在 DTO 上进行校验防止非法参数。SQL 注入与 XSSMyBatis 使用预编译语句天然防 SQL 注入。XSS 攻击可以通过在返回前端时对字符串内容进行转义如使用 Hutool 的HtmlUtil.escape来防护。5. 工程化实践与部署运维一个可维护、易部署的后端项目离不开良好的工程化实践。5.1 分层架构与包结构我采用经典的四层架构com.xxx.sports ├── config // 配置类WebMvcConfig, SecurityConfig, RedisConfig等 ├── controller // 控制层接收请求调用Service返回结果 ├── service // 业务逻辑层 │ ├── impl // 服务实现类 ├── mapper // 数据访问层MyBatis Mapper接口 ├── entity // 实体类与数据库表对应 ├── dto // 数据传输对象用于接口入参、出参 ├── vo // 视图对象用于返回给前端的特定数据结构 ├── utils // 工具类库 ├── aspect // AOP切面日志、权限等 ├── exception // 自定义异常和全局异常处理器 └── SportsApplication.java // 主启动类DTO 和 VO 的区分这是保持接口清晰的关键。DTO如RegistrationQueryDTO用于封装前端传入的复杂查询条件。VO如RegistrationVO用于封装返回给前端的、可能聚合了多张表数据的对象。避免直接使用Entity作为接口参数或返回值这会导致实体类过度暴露数据库细节不利于安全和后续修改。5.2 接口文档与前后端协作使用SpringDoc OpenAPISwagger 3自动生成 API 文档。在 Maven 中引入springdoc-openapi-ui依赖在主启动类同级目录下添加一个配置类即可。Configuration public class OpenApiConfig { Bean public OpenAPI sportsOpenAPI() { return new OpenAPI() .info(new Info().title(运动会报名管理系统 API) .description(后端接口文档) .version(v1.0.0) .contact(new Contact().name(开发者).email(devexample.com))) .externalDocs(new ExternalDocumentation() .description(项目Wiki) .url(https://github.com/your-repo/wiki)); } }然后在 Controller 和 Model 上使用Operation,Parameter,Schema等注解描述接口和模型。访问/swagger-ui.html即可看到交互式文档。这极大地提升了前后端的沟通效率。5.3 配置文件与多环境部署使用application.yml进行配置并通过application-dev.yml,application-prod.yml区分开发、生产环境。关键配置包括# application.yml spring: profiles: active: activatedProperties # Maven变量打包时指定 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/sports_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: ${DB_PASSWORD:123456} # 支持从环境变量读取默认值123456 redis: host: localhost port: 6379 password: ${REDIS_PASSWORD:} mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境开启SQL日志 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段 logic-delete-value: 1 logic-not-delete-value: 0 # 自定义配置 sports: file: upload-path: /data/uploads # 生产环境上传目录通过spring.profiles.active指定激活的环境。在服务器上可以通过java -jar sports-app.jar --spring.profiles.activeprod启动生产环境应用。5.4 日志与监控日志是排查线上问题的生命线。使用SLF4J Logback组合。配置 logback-spring.xml在 resources 目录下创建按天滚动生成日志文件区分 INFO、ERROR 级别并控制单个文件大小。关键位置打日志在 Service 层的重要方法入口、出口、异常捕获处使用Slf4j注解注入 logger记录入参、关键步骤结果和错误信息。避免过度打印日志影响性能也避免什么都不打印。使用 AOP 统一记录请求日志可以创建一个切面自动记录所有 Controller 方法的请求路径、参数、耗时、响应结果脱敏后和 IP这对于审计和性能分析非常有用。监控集成 Spring Boot Actuator暴露/actuator/health,/actuator/metrics等端点可以方便地集成到 Prometheus Grafana 监控体系中查看应用健康状况、JVM 内存、GC、请求量等指标。6. 常见问题排查与性能优化实录在实际开发和部署中总会遇到各种各样的问题。这里记录几个典型场景和解决思路。6.1 典型问题排查表问题现象可能原因排查步骤与解决方案报名时提示“网络繁忙请重试”1. 乐观锁更新失败。2. 数据库连接池耗尽。3. 服务器负载过高。1. 查看业务日志确认是否是update ... set current_participants... where ...语句返回的affected rows为0。2. 查看数据库连接池监控如 HikariCP 的spring.datasource.hikari配置调大maximum-pool-size。3. 检查服务器 CPU、内存使用率查看应用日志是否有大量错误或慢查询。分页查询速度越来越慢1. 缺少有效索引。2. 偏移量offset过大深分页问题。3. 返回数据量过大。1. 使用EXPLAIN分析慢查询 SQL在WHERE、ORDER BY、GROUP BY涉及的字段上添加索引。2. 对于深分页考虑使用“游标分页”WHERE id last_id LIMIT size替代LIMIT offset, size。3. 前端限制每页大小后端检查查询是否必要返回全部字段。前端获取到的 JSON 日期格式混乱Jackson 序列化/反序列化时区或格式问题。1. 在application.yml中统一配置spring.jackson.time-zone: GMT8和spring.jackson.date-format: yyyy-MM-dd HH:mm:ss。2. 或在具体的日期字段上使用JsonFormat(pattern”yyyy-MM-dd HH:mm:ss”, timezone”GMT8″)注解。部署后无法连接数据库1. 数据库地址、端口、用户名密码错误。2. 服务器防火墙未开放数据库端口。3. MySQL 用户权限不足或未允许远程连接。1. 检查application-prod.yml配置。2. 在服务器上使用telnet db_host db_port测试连通性。3. 登录 MySQL检查用户权限GRANT ALL PRIVILEGES ON sports_db.* TO ‘username’’%’ IDENTIFIED BY ‘password’; FLUSH PRIVILEGES;(生产环境建议限制IP)上传文件失败如运动员照片1. 上传目录不存在或应用无写入权限。2. 文件大小超过 Spring Boot 默认限制1MB。3. 文件名包含特殊字符或路径遍历漏洞。1. 创建目录并赋予权限mkdir -p /data/uploads chmod 755 /data/uploads。2. 在配置文件中调整spring.servlet.multipart.max-file-size10MB和max-request-size10MB。3. 在后端对上传的文件名进行重命名如使用UUID并校验文件后缀名。6.2 性能优化点缓存应用对于不常变但高频访问的数据使用 Redis 缓存。例如运动会的项目列表、热门项目的已报名人数可缓存一个近似值配合乐观锁更新。使用 Spring Cache 抽象Cacheable,CacheEvict可以优雅地集成。数据库连接池调优根据实际并发量和数据库性能调整 HikariCP 的maximum-pool-size通常建议在 10-20 之间并非越大越好、connection-timeout、idle-timeout等参数。SQL 优化定期使用慢查询日志工具如 MySQL 的slow_query_log找出执行慢的 SQL用EXPLAIN分析执行计划针对性优化加索引、改写 SQL。JVM 参数调优在生产环境根据服务器内存大小设置合理的堆内存参数-Xms,-Xmx并选择合适的垃圾收集器如 G1。静态资源分离将用户上传的图片、文件等静态资源使用 Nginx 提供直接访问减轻应用服务器压力。可以通过配置文件将上传路径映射到 Nginx 的访问路径。6.3 一个真实的“坑”Long类型ID在前端的精度丢失这是前后端联调时一个非常隐蔽的问题。JavaScript 的 Number 类型能安全表示的最大整数是2^53 - 1约9e15而 Java 的 Long 类型最大值是2^63 - 1。当后端的 Long 类型 ID比如雪花算法生成的19位ID以 JSON 形式传到前端时如果数值超过了2^53 - 1JavaScript 解析时就会丢失精度导致 ID 值变化进而引发一系列找不到数据的问题。解决方案在后端返回 JSON 时将 Long 类型 ID 序列化为字符串。全局配置推荐在application.yml中配置 Jackson。spring: jackson: generator: write-numbers-as-strings: true # 将所有数字写成字符串可能影响其他数字类型或者更精准地创建一个自定义的 Jackson 配置类针对Long和BigInteger类型注册一个序列化器。Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder - { builder.serializerByType(Long.class, ToStringSerializer.instance); builder.serializerByType(Long.TYPE, ToStringSerializer.instance); }; } }局部注解在特定的实体类字段上使用JsonSerialize(using ToStringSerializer.class)。这种方式更灵活但需要修改每个实体类。踩过这个坑之后我现在在新项目设计之初就会把全局的 Long 转 String 配置加上防患于未然。7. 项目扩展与进阶思考这个基础的报名管理系统完成后还可以根据实际需求进行很多有价值的扩展微信小程序/公众号集成这是非常自然的延伸。学生通过微信扫码或公众号菜单即可报名、查赛程、看成绩体验更佳。后端需要提供一套适配微信生态的 API可能涉及微信登录、消息模板推送。实时消息通知报名成功、审核结果、成绩发布时通过 WebSocket 或集成消息推送服务如极光推送、腾讯云信给用户发送实时通知。数据可视化大屏为运动会现场或领导展示使用 ECharts 等库开发实时展示报名总人数、各学院参赛热度、破纪录情况等数据的大屏页面。自动化编排与赛程生成对于需要预赛、决赛的项目可以根据报名人数自动生成分组赛程表、晋级规则这是一个更有挑战性的算法问题。微服务化改造如果系统规模扩大可以考虑拆分为用户中心、报名服务、成绩服务、消息服务等独立的微服务使用 Spring Cloud AlibabaNacos, Sentinel, Seata等技术栈来治理提升系统的弹性和可扩展性。回过头看这个基于 Spring Boot 和 Vue 的运动会报名管理系统虽然业务场景具体但其中涉及的技术点——RESTful API 设计、数据库事务与并发控制、权限管理、前后端分离、工程化部署——都是现代 Web 开发的通用核心。把这样一个项目从头到尾扎扎实实地做一遍并且深入思考每个环节背后的“为什么”远比肤浅地了解十个框架更有价值。在编码之外如何设计一张健壮的表如何保证在高并发下数据不错乱如何让代码更清晰易维护这些才是真正体现工程师功力的地方。本文还有配套的精品资源点击获取
返回列表