最近在帮一个体育学校做信息化升级,发现他们的招生管理还停留在Excel表格和纸质档案阶段,信息分散、统计困难、流程混乱。正好借着这个机会,我基于SpringBoot开发了一套完整的招生信息管理平台,涵盖了从学生报名、资料审核、成绩录入到录取通知的全流程。这套方案不仅解决了他们的痛点,而且代码结构清晰、易于二次开发,非常适合作为SpringBoot的实战项目来学习。
本文将手把手带你从零搭建一个“体育学校招生信息管理平台”。无论你是刚接触SpringBoot想找个综合项目练手,还是需要为学校或培训机构开发类似系统,都能从中获得一套可直接复用的代码和清晰的实现思路。我们会重点讲解SpringBoot如何整合MyBatis-Plus、Spring Security、Redis等核心组件,并实现如分页查询、数据导出、权限控制等实用功能。
1. 项目背景与核心需求分析
在深入代码之前,我们首先要明确这个平台要解决什么问题。传统的体育学校招生管理通常面临以下挑战:
- 信息孤岛 :学生基本信息、体测成绩、文化课成绩、报名材料分散在不同老师手中,难以统一查看和分析。
- 流程繁琐 :从报名到录取,需要经过多轮审核和签字,依赖人工传递,效率低下且容易出错。
- 数据统计难 :招生负责人想了解各项目报名人数、生源分布、成绩分布等情况,需要手动从多个表格中汇总,工作量大。
- 安全性低 :学生敏感信息(如身份证号、联系方式)可能通过U盘、微信随意传播,存在泄露风险。
因此,我们设计的平台需要满足以下核心需求:
- 多角色权限管理 :区分系统管理员、招生办老师、项目教练、学生/家长等角色,不同角色看到和操作的数据不同。
- 全流程线上化 :实现在线报名、材料上传、多级审核、成绩录入、预录取/录取通知发布。
- 灵活的数据管理 :对学生信息、招生项目、体测标准等进行增删改查,并支持复杂条件查询和分页。
- 数据可视化与导出 :提供仪表盘展示关键招生数据,并支持将报名表、录取名单等导出为Excel或PDF。
- 系统安全与性能 :保证用户认证与授权安全,对高频访问的数据(如招生简章)进行缓存,提升系统响应速度。
技术选型上,我们采用当前企业级开发中最流行、最成熟的SpringBoot全家桶方案,确保项目的稳定性和可维护性。
2. 技术栈与开发环境准备
一个清晰的技术栈和稳定的开发环境是项目成功的基石。以下是本项目采用的核心技术及版本建议。
后端技术栈:
- 核心框架 :Spring Boot 2.7.x (这是一个长期支持版本,生态稳定)
- 持久层框架 :MyBatis-Plus 3.5.x (极大简化CRUD操作)
- 安全框架 :Spring Security (处理认证和授权)
- 缓存 :Redis (存储会话、缓存热点数据)
- 数据库 :MySQL 8.0
- 模板引擎 :Thymeleaf (用于服务端渲染简单页面,也可替换为前后端分离模式)
- 工具库 :Hutool、Lombok、Apache POI (Excel操作)
- 构建工具 :Maven 3.6+
前端技术栈 (本项目以教学后端为主,前端简化):
- 模板 :使用Thymeleaf + Bootstrap 5 + jQuery,快速构建管理后台页面。
- 说明 :在实际大型项目中,更推荐前后端分离,使用Vue.js或React作为前端框架,通过RESTful API与后端交互。本文为聚焦SpringBoot后端逻辑,前端仅提供最小化实现。
开发环境与工具:
- JDK :17 (推荐) 或 1.8
- IDE :IntelliJ IDEA (社区版或旗舰版均可)
- 数据库工具 :Navicat 或 MySQL Workbench
- API测试 :Postman 或 Insomnia
- 版本控制 :Git
版本兼容性说明
:
Spring Boot 2.7.x 与 JDK 17 兼容性良好。MyBatis-Plus 3.5.x 是对应此SpringBoot版本的常用选择。在
pom.xml
中统一管理依赖版本是关键,避免版本冲突。下面我们就开始创建项目。
3. 初始化SpringBoot项目与基础配置
我们使用IntelliJ IDEA的Spring Initializr来快速初始化项目。
步骤1:创建项目
打开IDEA,选择
File -> New -> Project
,选择
Spring Initializr
。填写项目信息:
-
Group:
com.sportschool -
Artifact:
admission-platform - Type: Maven
- Java Version: 17
- Packaging: Jar
步骤2:选择初始依赖 在Dependencies中选择:
- Spring Web (构建Web应用)
- Thymeleaf (模板引擎)
- MyBatis Framework (先选这个,后续我们会改为MyBatis-Plus)
- MySQL Driver (数据库驱动)
- Lombok (简化实体类代码)
点击创建,IDEA会自动生成项目结构并下载依赖。
步骤3:调整pom.xml,引入MyBatis-Plus等关键依赖
创建完成后,我们需要修改
pom.xml
,将MyBatis替换为功能更强大的MyBatis-Plus,并添加其他必要依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version> <!-- 使用稳定的2.7.x版本 -->
<relativePath/>
</parent>
<groupId>com.sportschool</groupId>
<artifactId>admission-platform</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>admission-platform</name>
<description>Sports School Admission Management Platform</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<hutool.version>5.8.20</hutool.version>
</properties>
<dependencies>
<!-- Spring Boot 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Security 用于权限管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot 缓存抽象与 Redis 实现 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 持久层:MyBatis-Plus (替代官方的mybatis-spring-boot-starter) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Excel操作 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
步骤4:配置数据库和MyBatis-Plus
在
src/main/resources/application.yml
中配置应用信息、数据源和MyBatis-Plus。
server:
port: 8080
servlet:
context-path: /admission
spring:
application:
name: admission-platform
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sports_admission?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: your_password # 请替换为你的数据库密码
# Redis配置
redis:
host: localhost
port: 6379
password: # 如果没有密码则留空
database: 0
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
# 缓存配置
cache:
type: redis
redis:
time-to-live: 3600000 # 缓存过期时间,单位毫秒,这里设置1小时
# Thymeleaf配置
thymeleaf:
cache: false # 开发阶段关闭缓存,修改页面立即生效
encoding: UTF-8
mode: HTML
prefix: classpath:/templates/
suffix: .html
# MyBatis-Plus 配置
mybatis-plus:
configuration:
# 控制台打印SQL日志(开发环境开启,生产环境关闭)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true # 自动将下划线命名转为驼峰命名
global-config:
db-config:
id-type: auto # 主键策略,数据库自增
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
mapper-locations: classpath:mapper/*.xml # XML映射文件位置,如果使用纯注解可省略
步骤5:创建数据库
在MySQL中创建数据库
sports_admission
,字符集建议使用
utf8mb4
。
CREATE DATABASE IF NOT EXISTS `sports_admission` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
至此,项目的基础骨架和配置已经完成。接下来,我们将开始设计数据库和创建实体类。
4. 数据库设计与实体类创建
根据招生管理流程,我们设计几个核心表。这里先给出关键表结构,实际项目中可能更复杂。
4.1 用户表 (sys_user) 存储系统所有用户信息,包括管理员、招生老师、教练等。
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(50) NOT NULL COMMENT '用户名(登录账号)',
`password` varchar(100) NOT NULL COMMENT '加密后的密码',
`real_name` varchar(20) DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`role` varchar(20) NOT NULL DEFAULT 'STUDENT' COMMENT '角色:ADMIN, ADMISSION_TEACHER, COACH, STUDENT',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
`deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除:0-未删除,1-已删除',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
KEY `idx_role_status` (`role`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户表';
4.2 招生项目表 (admission_project) 定义每年的招生项目,如“2025年篮球特长生招生”。
CREATE TABLE `admission_project` (
`id` bigint NOT NULL AUTO_INCREMENT,
`project_name` varchar(100) NOT NULL COMMENT '项目名称',
`year` int NOT NULL COMMENT '招生年份',
`sport_type` varchar(50) NOT NULL COMMENT '体育项目类型:BASKETBALL, FOOTBALL, SWIMMING等',
`description` text COMMENT '项目描述',
`start_time` datetime NOT NULL COMMENT '报名开始时间',
`end_time` datetime NOT NULL COMMENT '报名结束时间',
`max_quota` int DEFAULT NULL COMMENT '最大招生名额',
`status` varchar(20) DEFAULT 'DRAFT' COMMENT '状态:DRAFT, PUBLISHED, CLOSED',
`deleted` tinyint DEFAULT '0',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='招生项目表';
4.3 学生报名表 (student_application) 学生提交的报名申请。
CREATE TABLE `student_application` (
`id` bigint NOT NULL AUTO_INCREMENT,
`project_id` bigint NOT NULL COMMENT '招生项目ID',
`student_name` varchar(50) NOT NULL COMMENT '学生姓名',
`gender` varchar(10) DEFAULT NULL COMMENT '性别',
`id_card` varchar(20) NOT NULL COMMENT '身份证号',
`birth_date` date DEFAULT NULL COMMENT '出生日期',
`phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`guardian_phone` varchar(20) DEFAULT NULL COMMENT '监护人电话',
`school` varchar(100) DEFAULT NULL COMMENT '毕业/在读学校',
`sport_level` varchar(50) DEFAULT NULL COMMENT '运动等级',
`application_form_url` varchar(255) DEFAULT NULL COMMENT '报名表附件URL',
`physical_test_score` decimal(5,2) DEFAULT NULL COMMENT '体测成绩',
`written_test_score` decimal(5,2) DEFAULT NULL COMMENT '文化课成绩',
`total_score` decimal(5,2) DEFAULT NULL COMMENT '综合总分',
`review_status` varchar(20) DEFAULT 'PENDING' COMMENT '审核状态:PENDING, FIRST_REVIEW_PASS, FIRST_REVIEW_REJECT, FINAL_REVIEW_PASS, FINAL_REVIEW_REJECT',
`review_comment` varchar(500) DEFAULT NULL COMMENT '审核意见',
`admission_status` varchar(20) DEFAULT 'NOT_ADMITTED' COMMENT '录取状态:NOT_ADMITTED, PRE_ADMITTED, ADMITTED',
`deleted` tinyint DEFAULT '0',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_project_id_card` (`project_id`,`id_card`), -- 同一项目下身份证号唯一
KEY `idx_project_status` (`project_id`,`review_status`,`admission_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生报名表';
4.4 创建对应的Java实体类 使用MyBatis-Plus,我们需要为每个表创建对应的实体类,并使用Lombok简化代码。
// 文件路径:src/main/java/com/sportschool/admissionplatform/entity/SysUser.java
package com.sportschool.admissionplatform.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_user") // 指定表名
public class SysUser {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String username;
private String password;
private String realName;
private String phone;
private String email;
private String avatar;
private String role; // 角色编码
private Integer status;
@TableLogic // 逻辑删除注解
private Integer deleted;
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充
private LocalDateTime updateTime;
}
// 文件路径:src/main/java/com/sportschool/admissionplatform/entity/StudentApplication.java
package com.sportschool.admissionplatform.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("student_application")
public class StudentApplication {
@TableId(type = IdType.AUTO)
private Long id;
private Long projectId;
private String studentName;
private String gender;
private String idCard;
private LocalDate birthDate;
private String phone;
private String guardianPhone;
private String school;
private String sportLevel;
private String applicationFormUrl;
private BigDecimal physicalTestScore;
private BigDecimal writtenTestScore;
private BigDecimal totalScore;
private String reviewStatus;
private String reviewComment;
private String admissionStatus;
@TableLogic
private Integer deleted;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
4.5 配置MyBatis-Plus的元数据处理器
为了实现
createTime
和
updateTime
的自动填充,我们需要创建一个
MetaObjectHandler
。
// 文件路径:src/main/java/com/sportschool/admissionplatform/config/MyMetaObjectHandler.java
package com.sportschool.admissionplatform.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
数据库和实体层搭建好后,我们就可以编写数据访问层(Mapper)和服务层(Service)了。MyBatis-Plus的强大之处在于,对于简单的CRUD,我们甚至可以不写SQL。
5. 使用MyBatis-Plus实现高效数据访问
MyBatis-Plus(简称MP)是对MyBatis的增强,提供了通用的Mapper和Service,可以极大减少单表操作的代码量。
5.1 创建Mapper接口
Mapper接口继承MP提供的
BaseMapper
,即可获得大量开箱即用的方法。
// 文件路径:src/main/java/com/sportschool/admissionplatform/mapper/StudentApplicationMapper.java
package com.sportschool.admissionplatform.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sportschool.admissionplatform.entity.StudentApplication;
import org.apache.ibatis.annotations.Mapper;
@Mapper // 别忘了这个注解,让Spring能扫描到
public interface StudentApplicationMapper extends BaseMapper<StudentApplication> {
// 如果需要复杂的多表查询,可以在这里定义方法,并在对应的XML中写SQL
// 例如:List<StudentApplication> selectApplicationsWithProjectInfo(Map<String, Object> params);
}
同样地,创建
SysUserMapper
和
AdmissionProjectMapper
。
5.2 创建Service接口和实现类
Service层继承MP的
IService
,实现类继承
ServiceImpl
并实现自己的接口。
// 文件路径:src/main/java/com/sportschool/admissionplatform/service/IStudentApplicationService.java
package com.sportschool.admissionplatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sportschool.admissionplatform.entity.StudentApplication;
import java.util.Map;
public interface IStudentApplicationService extends IService<StudentApplication> {
// 自定义业务方法
boolean submitApplication(StudentApplication application);
Map<String, Object> getApplicationPage(Integer pageNum, Integer pageSize, String keyword, String reviewStatus);
}
// 文件路径:src/main/java/com/sportschool/admissionplatform/service/impl/StudentApplicationServiceImpl.java
package com.sportschool.admissionplatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sportschool.admissionplatform.entity.StudentApplication;
import com.sportschool.admissionplatform.mapper.StudentApplicationMapper;
import com.sportschool.admissionplatform.service.IStudentApplicationService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
@Service
public class StudentApplicationServiceImpl extends ServiceImpl<StudentApplicationMapper, StudentApplication> implements IStudentApplicationService {
@Override
public boolean submitApplication(StudentApplication application) {
// 业务逻辑:检查项目是否在报名期内、身份证号是否重复等
// 这里简化处理,直接保存
return this.save(application);
}
@Override
public Map<String, Object> getApplicationPage(Integer pageNum, Integer pageSize, String keyword, String reviewStatus) {
// 构建分页对象
Page<StudentApplication> page = new Page<>(pageNum, pageSize);
// 构建查询条件
LambdaQueryWrapper<StudentApplication> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(keyword), StudentApplication::getStudentName, keyword)
.or()
.like(StringUtils.hasText(keyword), StudentApplication::getIdCard, keyword);
wrapper.eq(StringUtils.hasText(reviewStatus), StudentApplication::getReviewStatus, reviewStatus);
wrapper.orderByDesc(StudentApplication::getCreateTime); // 按创建时间倒序
// 执行分页查询
IPage<StudentApplication> applicationPage = this.page(page, wrapper);
// 封装返回结果
Map<String, Object> result = new HashMap<>();
result.put("list", applicationPage.getRecords());
result.put("total", applicationPage.getTotal());
result.put("pages", applicationPage.getPages());
result.put("current", applicationPage.getCurrent());
result.put("size", applicationPage.getSize());
return result;
}
}
5.3 配置MyBatis-Plus分页插件 要让分页查询生效,需要在配置类中注册分页插件。
// 文件路径:src/main/java/com/sportschool/admissionplatform/config/MybatisPlusConfig.java
package com.sportschool.admissionplatform.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 还可以添加其他插件,如乐观锁插件
// interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
至此,数据访问层已经非常完善了。接下来,我们将集成Spring Security来构建系统的安全防线。
6. 集成Spring Security实现权限控制
招生系统涉及敏感信息,必须进行严格的权限控制。Spring Security是Spring生态中事实上的安全标准。
6.1 核心概念与配置
我们采用基于角色的访问控制(RBAC)。用户拥有角色(如ADMIN),角色关联权限(如
student:view
,
student:edit
)。
首先,创建一个安全配置类。
// 文件路径:src/main/java/com/sportschool/admissionplatform/config/SecurityConfig.java
package com.sportschool.admissionplatform.config;
import com.sportschool.admissionplatform.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
/**
* 密码加密器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义的UserDetailsService和密码加密器
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 静态资源和登录页面允许所有人访问
.antMatchers("/css/**", "/js/**", "/images/**", "/webjars/**").permitAll()
.antMatchers("/login", "/register", "/api/public/**").permitAll()
// 权限控制:按角色控制访问路径
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/teacher/**").hasAnyRole("ADMIN", "ADMISSION_TEACHER")
.antMatchers("/coach/**").hasAnyRole("ADMIN", "COACH")
.antMatchers("/student/**").hasAnyRole("ADMIN", "STUDENT")
// 其他所有请求都需要认证
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") // 自定义登录页面路径
.loginProcessingUrl("/doLogin") // 登录表单提交的路径
.defaultSuccessUrl("/index", true) // 登录成功后的默认跳转路径
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
.rememberMe() // 记住我功能
.and()
.csrf().disable() // 开发阶段可禁用CSRF,生产环境建议开启
.headers().frameOptions().sameOrigin(); // 允许同源iframe嵌入
}
}
6.2 实现自定义UserDetailsService
这个服务负责根据用户名从数据库加载用户信息,并封装成Spring Security认识的
UserDetails
对象。
// 文件路径:src/main/java/com/sportschool/admissionplatform/service/impl/UserDetailsServiceImpl.java
package com.sportschool.admissionplatform.service.impl;
import com.sportschool.admissionplatform.entity.SysUser;
import com.sportschool.admissionplatform.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查询用户
SysUser sysUser = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
if (sysUser == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 2. 检查用户状态
if (sysUser.getStatus() == 0) {
throw new UsernameNotFoundException("用户已被禁用");
}
// 3. 组装权限/角色
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 角色需要加上`ROLE_`前缀
authorities.add(new SimpleGrantedAuthority("ROLE_" + sysUser.getRole()));
// 还可以根据角色查询具体的权限列表添加进来
// authorities.add(new SimpleGrantedAuthority("student:view"));
// 4. 返回UserDetails对象
return new User(
sysUser.getUsername(),
sysUser.getPassword(), // 数据库存储的应该是加密后的密码
authorities
);
}
}
6.3 密码加密存储
在用户注册或修改密码时,必须使用
PasswordEncoder
对密码进行加密。
// 在Service中注入PasswordEncoder
@Autowired
private PasswordEncoder passwordEncoder;
public boolean registerUser(SysUser user) {
// 检查用户名是否已存在...
// 加密密码
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
// 保存用户...
return save(user);
}
6.4 在Controller或Service中获取当前用户信息 在业务代码中,我们经常需要知道当前登录的用户是谁。
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
public String getCurrentUsername() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
} else {
return principal.toString();
}
}
安全框架集成后,我们的系统就有了基本的门户守卫。接下来,我们实现一个核心业务功能:学生报名信息的导出。
7. 使用Apache POI实现数据导出功能
招生办经常需要将报名数据导出为Excel进行线下分析或上报。Apache POI是Java操作Microsoft Office文档的强大库。
7.1 添加依赖
已在
pom.xml
中添加了
poi-ooxml
依赖。
7.2 创建导出服务类 我们将创建一个工具类,专门负责将学生报名列表写入Excel。
// 文件路径:src/main/java/com/sportschool/admissionplatform/service/impl/ExcelExportService.java
package com.sportschool.admissionplatform.service.impl;
import com.sportschool.admissionplatform.entity.StudentApplication;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Service
public class ExcelExportService {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public void exportStudentApplications(List<StudentApplication> applications, HttpServletResponse response) throws IOException {
// 1. 创建工作簿和工作表
Workbook workbook = new XSSFWorkbook(); // 创建.xlsx格式的工作簿
Sheet sheet = workbook.createSheet("学生报名信息");
// 2. 创建标题行
Row headerRow = sheet.createRow(0);
String[] headers = {"ID", "姓名", "身份证号", "性别", "出生日期", "联系电话", "毕业学校", "运动等级", "体测成绩", "文化课成绩", "综合总分", "审核状态", "报名时间"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
// 可以设置标题样式
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
cell.setCellStyle(headerStyle);
}
// 3. 填充数据行
int rowNum = 1;
for (StudentApplication app : applications) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(app.getId());
row.createCell(1).setCellValue(app.getStudentName());
row.createCell(2).setCellValue(app.getIdCard());
row.createCell(3).setCellValue(app.getGender());
row.createCell(4).setCellValue(app.getBirthDate() != null ? app.getBirthDate().toString() : "");
row.createCell(5).setCellValue(app.getPhone());
row.createCell(6).setCellValue(app.getSchool());
row.createCell(7).setCellValue(app.getSportLevel());
row.createCell(8).setCellValue(app.getPhysicalTestScore() != null ? app.getPhysicalTestScore().doubleValue() : 0.0);
row.createCell(9).setCellValue(app.getWrittenTestScore() != null ? app.getWrittenTestScore().doubleValue() : 0.0);
row.createCell(10).setCellValue(app.getTotalScore() != null ? app.getTotalScore().doubleValue() : 0.0);
row.createCell(11).setCellValue(translateStatus(app.getReviewStatus()));
row.createCell(12).setCellValue(app.getCreateTime() != null ? app.getCreateTime().format(DATE_FORMATTER) : "");
}
// 4. 自动调整列宽(粗略)
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 5. 设置HTTP响应头,触发文件下载
String fileName = "学生报名信息_" + System.currentTimeMillis() + ".xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 6. 将工作簿写入响应输出流
workbook.write(response.getOutputStream());
workbook.close();
}
private String translateStatus(String status) {
// 将状态码翻译为中文
switch (status) {
case "PENDING": return "待初审";
case "FIRST_REVIEW_PASS": return "初审通过";
case "FIRST_REVIEW_REJECT": return "初审驳回";
case "FINAL_REVIEW_PASS": return "终审通过";
case "FINAL_REVIEW_REJECT": return "终审驳回";
default: return status;
}
}
}
7.3 在Controller中调用导出服务
// 文件路径:src/main/java/com/sportschool/admissionplatform/controller/ExportController.java
package com.sportschool.admissionplatform.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sportschool.admissionplatform.entity.StudentApplication;
import com.sportschool.admissionplatform.service.IStudentApplicationService;
import com.sportschool.admissionplatform.service.impl.ExcelExportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/api/export")
public class ExportController {
@Autowired
private IStudentApplicationService applicationService;
@Autowired
private ExcelExportService excelExportService;
@GetMapping("/studentExcel")
public void exportStudentExcel(@RequestParam(required = false) Long projectId,
@RequestParam(required = false) String reviewStatus,
HttpServletResponse response) throws IOException {
// 1. 构建查询条件
LambdaQueryWrapper<StudentApplication> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(projectId != null, StudentApplication::getProjectId, projectId);
wrapper.eq(reviewStatus != null, StudentApplication::getReviewStatus, reviewStatus);
wrapper.orderByDesc(StudentApplication::getCreateTime);
// 2. 查询数据(注意:数据量大时应分批次查询,这里演示用)
List<StudentApplication> list = applicationService.list(wrapper);
// 3. 调用导出服务
excelExportService.exportStudentApplications(list, response);
}
}
这样,访问
/api/export/studentExcel
接口,浏览器就会下载一个包含查询结果的Excel文件。这个功能非常实用,也是很多管理后台的标配。
8. 常见问题与排查思路(FAQ)
在开发和部署过程中,你可能会遇到以下问题。这里提供一些排查思路。
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
应用启动失败,报
Failed to configure a DataSource
|
1.
application.yml
中数据库连接信息错误。
2. MySQL服务未启动。 3. 依赖未正确引入。 |
1. 检查
url
,
username
,
password
。
2. 使用
mysql -u root -p
命令测试连接。
3. 检查
pom.xml
中
mysql-connector-java
依赖。
|
| MyBatis-Plus 分页查询不生效 |
1. 未配置分页插件。
2. 分页参数未正确传递。 |
1. 检查是否创建了
MybatisPlusConfig
并添加了
PaginationInnerInterceptor
。
2. 确保Service中使用了
Page
对象进行查询。
|
| Spring Security 登录后无限重定向到登录页 |
1. 登录成功后的跳转路径权限配置有误。
2. CSRF配置问题。 3. 记住我(Remember-Me)功能配置冲突。 |
1. 检查
.defaultSuccessUrl(“/index”)
中的
/index
路径是否允许对应角色访问。
2. 尝试暂时禁用CSRF(
.csrf().disable()
)。
3. 检查Session和Cookie配置。 |
插入数据时,
create_time
和
update_time
没有自动填充
|
1.
MetaObjectHandler
未生效。
2. 实体类字段注解错误。 3. 字段名与数据库列名不匹配。 |
1. 确保
MyMetaObjectHandler
类被
@Component
注解,且被Spring扫描到。
2. 检查实体类字段上的
@TableField(fill = FieldFill.INSERT)
等注解。
3. 检查数据库表字段名是否为
create_time
和
update_time
。
|
| 导出Excel时中文乱码或文件名错误 |
1. HTTP响应头编码问题。
2. 浏览器兼容性问题。 |
1. 确保设置
response.setCharacterEncoding(“UTF-8”)
,并使用
URLEncoder.encode(fileName, “UTF-8”)
编码文件名。
2. 对于旧版浏览器,可以尝试
filename*=UTF-8''
格式。
|
| Redis连接失败 |
1. Redis服务未启动。
2.
application.yml
中Redis配置错误(如密码、端口)。
3. 防火墙阻止了连接。 |
1. 使用
redis-cli ping
测试Redis服务。
2. 检查配置的host、port、password、database。 3. 检查服务器防火墙是否开放了Redis端口(默认6379)。 |
| 页面访问报403(禁止访问) |
1. 用户角色权限不足。
2. Spring Security的URL匹配规则配置过于严格。 |
1. 检查用户角色和
SecurityConfig
中对应路径的权限设置(如
.hasRole(‘ADMIN’)
)。
2. 检查登录用户的角色前缀
ROLE_
是否正确添加。
|
9. 项目部署与生产环境最佳实践
开发完成只是第一步,让系统稳定、安全地运行在生产环境更为关键。
9.1 配置文件分离
- 原则 :将开发、测试、生产环境的配置分离。
-
做法
:创建多个配置文件,如
application-dev.yml,application-prod.yml,在主application.yml中通过spring.profiles.active指定激活的环境。 - 敏感信息 :数据库密码、Redis密码、第三方API密钥等 绝不能 硬编码在配置文件中。应使用环境变量或配置中心(如Apollo, Nacos)管理。
9.2 数据库优化
-
索引
:在经常作为查询条件的字段上建立索引,如
student_application表的project_id,review_status,id_card。 -
SQL监控
:生产环境关闭MyBatis-Plus的SQL日志(
log-impl),避免性能损耗和日志膨胀。可以使用Druid连接池的监控功能。 -
连接池
:使用HikariCP(SpringBoot默认)或Druid,并合理配置
maximum-pool-size、connection-timeout等参数。
9.3 应用部署
-
打包
:使用
mvn clean package -DskipTests生成可执行的JAR文件。 -
进程管理
:不要直接用
java -jar启动。推荐使用系统服务(如Systemd)或进程管理工具(如Supervisor)来管理应用进程,实现开机自启、自动重启。 -
日志
:配置Logback或Log4j2,将日志按级别输出到文件,并设置合理的滚动策略和归档策略。生产环境日志级别通常设为
INFO或WARN。
9.4 安全加固
- HTTPS :为域名配置SSL证书,强制使用HTTPS访问。
- CSRF :生产环境务必启用Spring Security的CSRF保护。
-
SQL注入
:坚持使用MyBatis-Plus的Wrapper或
#{}预编译方式传参,严禁字符串拼接SQL。 - XSS防护 :对用户输入进行过滤和转义,或使用模板引擎(如Thymeleaf)的默认HTML转义功能。
-
权限校验
:除了URL层面的拦截,在Service层关键业务方法上,也应使用
@PreAuthorize注解进行方法级权限控制。
9.5 监控与告警
-
健康检查
:Spring Boot Actuator提供了
/actuator/health端点,可用于K8s存活探针或监控系统健康状态。 - 性能监控 :集成Micrometer和Prometheus,监控JVM内存、GC、线程池、HTTP请求延迟等指标。
- 业务日志 :在关键业务流程(如报名提交、审核通过)处打印结构化日志,便于后续排查问题和数据分析。
9.6 缓存策略
-
对于不经常变化但访问频繁的数据,如“招生项目列表”、“系统配置项”,可以使用Spring Cache注解(如
@Cacheable)将其缓存到Redis中,并设置合理的过期时间。 - 注意缓存穿透、缓存击穿、缓存雪崩问题,可以考虑使用空值缓存、互斥锁、随机过期时间等策略来缓解。
从零到一构建一个SpringBoot项目,并集成一系列企业级组件,是一个系统性的工程。本文详细介绍了体育学校招生信息管理平台的核心实现,涵盖了从项目初始化、数据库设计、数据访问、安全控制到数据导出的完整链路。这套代码结构清晰,模块划分明确,你可以直接在此基础上进行扩展,例如增加短信通知、在线支付报名费、生成准考证、与体测设备数据对接等功能。
技术的学习离不开实践,建议你按照文章步骤亲手搭建一遍,过程中遇到问题,再回头仔细查看配置和代码。掌握了这个项目,你不仅能够应对类似的校园管理系统开发,更能深刻理解SpringBoot生态下如何组织代码、如何解决常见的业务和安全问题。

38

被折叠的 条评论
为什么被折叠?



