SpringBoot+Vue3校运会管理系统开发实战

📅 2026/8/1 11:37:26
SpringBoot+Vue3校运会管理系统开发实战
1. 校运会管理系统技术架构解析这套校运会管理系统采用了当前企业级开发中最主流的技术组合SpringBootVue3MyBatis。这种前后端分离架构完美契合现代Web应用的开发需求后端使用Java技术栈处理业务逻辑前端用Vue3构建交互界面通过RESTful API进行数据通信。后端技术栈中SpringBoot 2.7.x作为基础框架内置Tomcat服务器通过starter依赖快速集成各类组件。MyBatis 3.5.x作为ORM框架配合pagehelper分页插件处理数据持久化。数据库选用MySQL 8.0利用InnoDB引擎的事务特性保证数据一致性。前端采用Vue3组合式API开发使用Vite作为构建工具相比Webpack具有更快的冷启动速度。Element Plus作为UI组件库配合Axios处理HTTP请求。前端路由使用Vue Router 4.x状态管理采用Pinia替代Vuex。技术选型心得这套技术组合经过了多个线上项目验证版本选择都避开了已知的兼容性问题。比如MyBatis特意选用3.5.10而不是最新的3.5.11因为后者与SpringBoot 2.7.x存在事务管理冲突。2. 数据库设计与核心表结构数据库设计遵循第三范式主要包含以下核心表用户表(sys_user)CREATE TABLE sys_user ( user_id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 学工号, password varchar(100) NOT NULL COMMENT 密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, college varchar(50) DEFAULT NULL COMMENT 学院, class_name varchar(50) DEFAULT NULL COMMENT 班级, role varchar(20) DEFAULT student COMMENT 角色(student/teacher/admin), avatar varchar(255) DEFAULT NULL COMMENT 头像URL, PRIMARY KEY (user_id), UNIQUE KEY idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;比赛项目表(sport_event)CREATE TABLE sport_event ( event_id int NOT NULL AUTO_INCREMENT, event_name varchar(100) NOT NULL COMMENT 项目名称, event_type varchar(20) NOT NULL COMMENT 田赛/径赛/球类, max_participants int DEFAULT NULL COMMENT 最大参赛人数, start_time datetime NOT NULL COMMENT 开始时间, location varchar(100) NOT NULL COMMENT 比赛地点, status tinyint DEFAULT 0 COMMENT 状态(0未开始/1进行中/2已结束), PRIMARY KEY (event_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;报名记录表(registration)CREATE TABLE registration ( reg_id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, event_id int NOT NULL, reg_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, group_num int DEFAULT NULL COMMENT 分组编号, lane_num int DEFAULT NULL COMMENT 道次编号, score varchar(20) DEFAULT NULL COMMENT 比赛成绩, ranking int DEFAULT NULL COMMENT 名次, PRIMARY KEY (reg_id), UNIQUE KEY idx_user_event (user_id,event_id), KEY idx_event (event_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设计要点所有表都使用utf8mb4字符集支持emoji外键关系通过应用层保证而非数据库约束便于分库分表扩展。status字段统一使用tinyint而非enum方便后期状态扩展。3. 后端核心模块实现3.1 安全认证模块采用JWTSpring Security实现认证授权关键配置类如下Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Autowired private JwtAuthenticationFilter jwtFilter; Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/**).permitAll() .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated(); http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); } Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }JWT工具类包含以下核心方法public class JwtUtils { private static final String SECRET your-256-bit-secret; private static final long EXPIRATION 86400L; // 24小时 public static String generateToken(UserDetails user) { return Jwts.builder() .setSubject(user.getUsername()) .claim(roles, user.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Authentication getAuthentication(String token) { Claims claims Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); String username claims.getSubject(); ListString roles claims.get(roles, List.class); ListSimpleGrantedAuthority authorities roles.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return new UsernamePasswordAuthenticationToken(username, null, authorities); } }3.2 比赛报名业务实现报名服务需要考虑并发控制使用数据库乐观锁防止超报Service public class RegistrationServiceImpl implements RegistrationService { Autowired private SportEventMapper eventMapper; Autowired private RegistrationMapper regMapper; Transactional(rollbackFor Exception.class) public Result register(Long userId, Integer eventId) { // 检查是否已报名 if (regMapper.existsByUserAndEvent(userId, eventId)) { return Result.fail(已报名该比赛); } // 获取比赛信息并校验 SportEvent event eventMapper.selectByIdForUpdate(eventId); if (event null) { return Result.fail(比赛不存在); } if (event.getStatus() ! 0) { return Result.fail(比赛已开始或结束); } // 检查剩余名额 int currentReg regMapper.countByEvent(eventId); if (currentReg event.getMaxParticipants()) { return Result.fail(报名人数已满); } // 创建报名记录 Registration reg new Registration(); reg.setUserId(userId); reg.setEventId(eventId); regMapper.insert(reg); return Result.success(报名成功); } }性能优化对于热门比赛可以在Redis中维护剩余名额计数器先进行预减库存再落数据库避免大量请求直接打到数据库。4. 前端关键功能实现4.1 比赛列表与报名使用Vue3的Composition API实现响应式比赛列表script setup import { ref, onMounted } from vue import { getEventList, registerEvent } from /api/sport import { useUserStore } from /stores/user const events ref([]) const loading ref(false) const userStore useUserStore() const fetchEvents async () { loading.value true try { const res await getEventList() events.value res.data } finally { loading.value false } } const handleRegister async (eventId) { try { await registerEvent(eventId) ElMessage.success(报名成功) fetchEvents() // 刷新列表 } catch (err) { ElMessage.error(err.message) } } onMounted(() { fetchEvents() }) /script template el-table :dataevents v-loadingloading el-table-column propeventName label比赛项目 / el-table-column propeventType label比赛类型 / el-table-column propstartTime label开始时间 / el-table-column label操作 template #default{row} el-button sizesmall clickhandleRegister(row.eventId) :disabledrow.status ! 0 || !userStore.isStudent 报名 /el-button /template /el-table-column /el-table /template4.2 成绩录入与排名管理员使用的成绩录入组件script setup import { ref } from vue import { submitScore } from /api/sport const props defineProps({ participants: Array, eventId: Number }) const formRef ref() const formData ref( props.participants.map(p ({ regId: p.regId, userId: p.userId, realName: p.realName, score: , ranking: null })) ) const rules { score: [{ required: true, message: 请输入成绩, trigger: blur }] } const handleSubmit async () { try { await formRef.value.validate() await submitScore(props.eventId, formData.value) ElMessage.success(成绩提交成功) emit(success) } catch (err) { console.error(err) } } /script template el-form :modelformData refformRef el-table :dataformData el-table-column proprealName label参赛者 / el-table-column label成绩 template #default{row, $index} el-form-item :prop${$index}.score :rulesrules.score el-input v-modelrow.score / /el-form-item /template /el-table-column el-table-column label名次 template #default{row, $index} el-input-number v-modelrow.ranking :min1 :maxparticipants.length / /template /el-table-column /el-table el-button typeprimary clickhandleSubmit提交成绩/el-button /el-form /template5. 系统部署与运维5.1 生产环境部署方案推荐使用Docker Compose部署整套系统version: 3.8 services: mysql: image: mysql:8.0 container_name: sport-mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: sport_db MYSQL_USER: sport_user MYSQL_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 restart: always backend: build: ./backend container_name: sport-backend environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/sport_db SPRING_DATASOURCE_USERNAME: sport_user SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD} ports: - 8080:8080 depends_on: - mysql restart: always frontend: build: ./frontend container_name: sport-frontend ports: - 80:80 restart: always volumes: mysql_data:后端Dockerfile示例FROM openjdk:11-jdk ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [java,-jar,/app.jar]前端Dockerfile示例FROM nginx:alpine COPY dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 805.2 性能优化配置Nginx配置优化示例server { listen 80; server_name your-domain.com; gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss text/javascript; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend: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; } }SpringBoot应用性能调优参数# 应用线程池配置 server.tomcat.max-threads200 server.tomcat.min-spare-threads20 # 数据库连接池 spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.minimum-idle5 spring.datasource.hikari.idle-timeout30000 # MyBatis缓存 mybatis.configuration.cache-enabledtrue mybatis.configuration.local-cache-scopestatement6. 常见问题解决方案6.1 跨域问题处理前后端分离项目常见的跨域问题可以通过以下方式解决后端配置SpringBootConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .maxAge(3600); } }前端开发环境代理配置vite.config.jsexport default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } })6.2 MyBatis动态SQL注入问题针对安全扫描报告的SQL注入风险正确处理方式错误示范存在注入风险select idfindUsers resultTypeUser SELECT * FROM sys_user WHERE username LIKE %${username}% /select正确做法使用#{}参数绑定select idfindUsers resultTypeUser SELECT * FROM sys_user WHERE username LIKE CONCAT(%, #{username}, %) /select复杂动态SQL使用 和 标签select idfindByCondition resultTypeUser SELECT * FROM sys_user where if testcollege ! null and college ! AND college #{college} /if if testclassName ! null and className ! AND class_name #{className} /if if testrole ! null and role ! AND role #{role} /if /where ORDER BY user_id DESC /select6.3 Vue3路由缓存问题保持列表页查询状态的处理方案使用keep-alive缓存组件template router-view v-slot{ Component } keep-alive :includecachedViews component :isComponent / /keep-alive /router-view /template在路由meta中配置缓存标识const routes [ { path: /event/list, component: EventList, meta: { keepAlive: true } } ]使用Pinia存储查询状态// stores/query.js export const useQueryStore defineStore(query, { state: () ({ eventQuery: { type: null, status: null, keyword: } }), actions: { saveEventQuery(query) { this.eventQuery query } } })7. 项目扩展方向7.1 微信小程序集成通过uni-app改造现有前端快速生成小程序版本安装uni-app依赖npm install -g vue/cli vue create -p dcloudio/uni-preset-vue sport-miniprogram复用现有Vue3组件需调整部分API调用方式// 替代axios的请求封装 const request (options) { return new Promise((resolve, reject) { uni.request({ url: baseURL options.url, method: options.method || GET, data: options.data || {}, success: (res) { if (res.data.code 200) { resolve(res.data) } else { uni.showToast({ title: res.data.message, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络错误, icon: none }) reject(err) } }) }) }7.2 数据分析模块使用ECharts实现比赛数据可视化安装依赖npm install echarts vue-echarts封装图表组件script setup import { use } from echarts/core import { CanvasRenderer } from echarts/renderers import { PieChart, BarChart } from echarts/charts import { TitleComponent, TooltipComponent, LegendComponent } from echarts/components import VChart from vue-echarts use([ CanvasRenderer, PieChart, BarChart, TitleComponent, TooltipComponent, LegendComponent ]) const props defineProps({ option: Object }) /script template v-chart :optionoption autoresize / /template比赛报名数据统计示例const getRegistrationStats async () { const res await getStatsData() option.value { title: { text: 各学院报名人数统计 }, tooltip: { trigger: axis }, xAxis: { type: category, data: res.data.map(item item.college) }, yAxis: { type: value }, series: [{ data: res.data.map(item item.count), type: bar, showBackground: true, backgroundStyle: { color: rgba(180, 180, 180, 0.2) } }] } }7.3 微服务化改造将单体应用拆分为微服务架构服务拆分方案用户服务处理认证授权、用户信息比赛服务管理比赛项目、赛程安排报名服务处理报名逻辑、成绩录入通知服务发送短信、站内信通知使用Spring Cloud Alibaba组件!-- Nacos服务发现 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency !-- OpenFeign服务调用 -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency !-- Sentinel流量控制 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependencyFeign客户端示例FeignClient(name user-service, path /api/user) public interface UserServiceClient { GetMapping(/{userId}) ResultUserVO getUserById(PathVariable Long userId); PostMapping(/batch) ResultListUserVO getUsersByIds(RequestBody ListLong userIds); }这套校运会管理系统从技术选型到实现细节都体现了现代Web开发的最佳实践前后端分离架构使得团队协作更加高效采用的主流技术栈也保证了系统的稳定性和可维护性。在实际部署时建议根据学校规模进行适当的性能调优特别是比赛报名高峰期要考虑增加限流措施。